mirror of
https://github.com/2006-Scape/apollo.git
synced 2026-07-02 16:49:12 +00:00
Initial implementation of ground items
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
plugin {
|
||||
name = "ground_item"
|
||||
packageName = "org.apollo.game.plugin.entity"
|
||||
authors = ["Ryley"]
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import org.apollo.game.message.impl.InventoryItemMessage
|
||||
|
||||
on { InventoryItemMessage::class }
|
||||
.where { option == 5 }
|
||||
.then {
|
||||
// This is just a stub, for now.
|
||||
// Several other things need to be done here:
|
||||
// - Items may only be dropped from your inventory
|
||||
// - Items dropped must be removed from your inventory
|
||||
// - Items must be checked to ensure they have a 'drop' option
|
||||
val item = it.inventory.get(slot)
|
||||
it.addGroundItem(item, it.position)
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import org.apollo.game.GameConstants
|
||||
import org.apollo.game.model.entity.GroundItem
|
||||
import org.apollo.game.scheduling.ScheduledTask
|
||||
|
||||
/**
|
||||
* A [ScheduledTask] that manages the globalization and expiration of [GroundItem]s.
|
||||
*/
|
||||
class GroundItemSynchronizationTask(private val groundItem: GroundItem) : ScheduledTask(DELAY, false) {
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* The delay between executions of this task, in pulses.
|
||||
*/
|
||||
const val DELAY = 1
|
||||
|
||||
/**
|
||||
* The amount of time, in pulses, in which this [GroundItem] will be globally visible.
|
||||
*/
|
||||
const val TRADABLE_TIME_UNTIL_GLOBAL = 60000 / GameConstants.PULSE_DELAY
|
||||
|
||||
/**
|
||||
* The amount of time, in pulses, in which this [GroundItem] will expire and be removed from the [World].
|
||||
*/
|
||||
const val UNTRADABLE_TIME_UNTIL_EXPIRE = 180000 / GameConstants.PULSE_DELAY
|
||||
|
||||
/**
|
||||
* The amount of time, in pulses, in which this [GroundItem] will expire and be removed from the [World].
|
||||
*/
|
||||
const val TIME_UNTIL_EXPIRE = 180000 / GameConstants.PULSE_DELAY
|
||||
}
|
||||
|
||||
/**
|
||||
* The amount of game pulses this [ScheduledTask] has been alive.
|
||||
*/
|
||||
private var pulses = 0
|
||||
|
||||
override fun execute() {
|
||||
val world = groundItem.world
|
||||
val owner = world.playerRepository[groundItem.ownerIndex]
|
||||
val untradable = false // TODO: item.getDefinition().isTradable();
|
||||
|
||||
if (!groundItem.isAvailable) {
|
||||
stop()
|
||||
return
|
||||
}
|
||||
|
||||
// Untradable items never go global
|
||||
if (untradable) {
|
||||
if (pulses >= UNTRADABLE_TIME_UNTIL_EXPIRE) {
|
||||
world.removeGroundItem(owner, groundItem)
|
||||
stop()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (groundItem.isGlobal) {
|
||||
if (pulses >= TIME_UNTIL_EXPIRE) {
|
||||
world.removeGroundItem(owner, groundItem)
|
||||
stop()
|
||||
}
|
||||
} else {
|
||||
if (pulses >= TRADABLE_TIME_UNTIL_GLOBAL) {
|
||||
groundItem.globalize()
|
||||
world.addGroundItem(owner, groundItem)
|
||||
}
|
||||
}
|
||||
|
||||
pulses++
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import org.apollo.game.message.impl.RemoveTileItemMessage
|
||||
import org.apollo.game.message.impl.SendTileItemMessage
|
||||
import org.apollo.game.model.Item
|
||||
import org.apollo.game.model.Position
|
||||
import org.apollo.game.model.World
|
||||
import org.apollo.game.model.entity.GroundItem
|
||||
import org.apollo.game.model.entity.Player
|
||||
|
||||
/**
|
||||
* Spawns a new local [GroundItem] for this Player at the specified [Position].
|
||||
*/
|
||||
fun Player.addGroundItem(item: Item, position: Position) {
|
||||
world.addGroundItem(this, GroundItem.dropped(world, position, item, this))
|
||||
}
|
||||
|
||||
internal fun World.addGroundItem(player: Player, item: GroundItem) {
|
||||
val region = regionRepository.fromPosition(item.position)
|
||||
|
||||
if (item.isGlobal) {
|
||||
region.addEntity(item, true)
|
||||
return
|
||||
}
|
||||
|
||||
groundItems.computeIfAbsent(player.encodedName, { HashSet<GroundItem>() }) += item
|
||||
|
||||
val offset = region.getPositionOffset(item)
|
||||
player.send(SendTileItemMessage(item.item, offset))
|
||||
|
||||
schedule(GroundItemSynchronizationTask(item))
|
||||
}
|
||||
|
||||
internal fun World.removeGroundItem(player: Player, item: GroundItem) {
|
||||
val region = regionRepository.fromPosition(item.position)
|
||||
|
||||
if (item.isGlobal) {
|
||||
region.removeEntity(item)
|
||||
}
|
||||
|
||||
val items = groundItems[player.encodedName] ?: return
|
||||
items -= item
|
||||
|
||||
val offset = region.getPositionOffset(item)
|
||||
player.send(RemoveTileItemMessage(item.item, offset))
|
||||
}
|
||||
@@ -1,8 +1,5 @@
|
||||
package org.apollo.game.model;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import org.apollo.Service;
|
||||
import org.apollo.cache.IndexedFileSystem;
|
||||
@@ -19,11 +16,7 @@ import org.apollo.game.model.area.Region;
|
||||
import org.apollo.game.model.area.RegionRepository;
|
||||
import org.apollo.game.model.area.collision.CollisionManager;
|
||||
import org.apollo.game.model.area.collision.CollisionUpdateListener;
|
||||
import org.apollo.game.model.entity.Entity;
|
||||
import org.apollo.game.model.entity.EntityType;
|
||||
import org.apollo.game.model.entity.MobRepository;
|
||||
import org.apollo.game.model.entity.Npc;
|
||||
import org.apollo.game.model.entity.Player;
|
||||
import org.apollo.game.model.entity.*;
|
||||
import org.apollo.game.model.event.Event;
|
||||
import org.apollo.game.model.event.EventListener;
|
||||
import org.apollo.game.model.event.EventListenerChainSet;
|
||||
@@ -33,6 +26,9 @@ import org.apollo.game.scheduling.Scheduler;
|
||||
import org.apollo.game.scheduling.impl.NpcMovementTask;
|
||||
import org.apollo.util.NameUtil;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* The world class is a singleton which contains objects like the {@link MobRepository} for players and NPCs. It should
|
||||
* only contain things relevant to the in-game world and not classes which deal with I/O and such (these may be better
|
||||
@@ -96,6 +92,11 @@ public final class World {
|
||||
*/
|
||||
private final MobRepository<Player> playerRepository = new MobRepository<>(WorldConstants.MAXIMUM_PLAYERS);
|
||||
|
||||
/**
|
||||
* A {@link Map} of player usernames to their local {@link Set} of {@link GroundItem}s.
|
||||
*/
|
||||
private final Map<Long, Set<GroundItem>> groundItems = new HashMap<>(WorldConstants.MAXIMUM_PLAYERS);
|
||||
|
||||
/**
|
||||
* A {@link Map} of player usernames and the player objects.
|
||||
*/
|
||||
@@ -181,6 +182,15 @@ public final class World {
|
||||
return playerRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link Map} of player usernames to their {@link Set} of {@link GroundItem}s
|
||||
*
|
||||
* @return The map.
|
||||
*/
|
||||
public Map<Long, Set<GroundItem>> getGroundItems() {
|
||||
return groundItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the plugin manager.
|
||||
*
|
||||
|
||||
@@ -12,6 +12,7 @@ import org.apollo.game.model.area.update.UpdateOperation;
|
||||
import org.apollo.game.model.entity.Entity;
|
||||
import org.apollo.game.model.entity.EntityType;
|
||||
import org.apollo.game.model.entity.obj.DynamicGameObject;
|
||||
import org.apollo.game.model.entity.obj.StaticGameObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
@@ -200,8 +201,8 @@ public final class Region {
|
||||
*/
|
||||
public Set<RegionUpdateMessage> encode(int height) {
|
||||
Set<RegionUpdateMessage> additions = entities.values().stream()
|
||||
.flatMap(Set::stream) // TODO fix this to work for ground items + projectiles
|
||||
.filter(entity -> entity instanceof DynamicGameObject && entity.getPosition().getHeight() == height)
|
||||
.flatMap(Set::stream) // TODO: Stop hurting my eyeballs.
|
||||
.filter(entity -> !entity.getEntityType().isMob() && !(entity instanceof StaticGameObject) && (!(entity instanceof DynamicGameObject) || entity.getPosition().getHeight() == height))
|
||||
.map(entity -> ((GroupableEntity) entity).toUpdateOperation(this, EntityUpdateType.ADD).toMessage())
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
@@ -269,6 +270,33 @@ public final class Region {
|
||||
return ImmutableSet.copyOf(filtered);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the position offset for the specified {@link Entity}.
|
||||
*
|
||||
* @param entity The Entity.
|
||||
* @return The position offset.
|
||||
*/
|
||||
public int getPositionOffset(Entity entity) {
|
||||
return getPositionOffset(entity.getPosition());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the position offset for the specified {@link Position}.
|
||||
*
|
||||
* @param position The Entity.
|
||||
* @return The position offset.
|
||||
*/
|
||||
public int getPositionOffset(Position position) {
|
||||
RegionCoordinates coordinates = getCoordinates();
|
||||
int dx = position.getX() - coordinates.getAbsoluteX();
|
||||
int dy = position.getY() - coordinates.getAbsoluteY();
|
||||
|
||||
Preconditions.checkArgument(dx >= 0 && dx < Region.SIZE, position + " not in expected Region of " + toString() + ".");
|
||||
Preconditions.checkArgument(dy >= 0 && dy < Region.SIZE, position + " not in expected Region of " + toString() + ".");
|
||||
|
||||
return dx << 4 | dy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link Set} of {@link RegionCoordinates} of Regions that are viewable from the specified
|
||||
* {@link Position}.
|
||||
|
||||
@@ -53,7 +53,7 @@ public abstract class UpdateOperation<E extends Entity> {
|
||||
* @return The RegionUpdateMessage.
|
||||
*/
|
||||
public final RegionUpdateMessage inverse() {
|
||||
int offset = getPositionOffset(entity.getPosition());
|
||||
int offset = region.getPositionOffset(entity);
|
||||
|
||||
switch (type) {
|
||||
case ADD:
|
||||
@@ -71,7 +71,7 @@ public abstract class UpdateOperation<E extends Entity> {
|
||||
* @return The Message.
|
||||
*/
|
||||
public final RegionUpdateMessage toMessage() {
|
||||
int offset = getPositionOffset(entity.getPosition());
|
||||
int offset = region.getPositionOffset(entity);
|
||||
|
||||
switch (type) {
|
||||
case ADD:
|
||||
@@ -99,21 +99,4 @@ public abstract class UpdateOperation<E extends Entity> {
|
||||
*/
|
||||
protected abstract RegionUpdateMessage remove(int offset);
|
||||
|
||||
/**
|
||||
* Gets the position offset for the specified {@link Position}.
|
||||
*
|
||||
* @param position The Position.
|
||||
* @return The position offset.
|
||||
*/
|
||||
private final int getPositionOffset(Position position) {
|
||||
RegionCoordinates coordinates = region.getCoordinates();
|
||||
int dx = position.getX() - coordinates.getAbsoluteX();
|
||||
int dy = position.getY() - coordinates.getAbsoluteY();
|
||||
|
||||
Preconditions.checkArgument(dx >= 0 && dx < Region.SIZE, position + " not in expected Region of " + region + ".");
|
||||
Preconditions.checkArgument(dy >= 0 && dy < Region.SIZE, position + " not in expected Region of " + region + ".");
|
||||
|
||||
return dx << 4 | dy;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -9,6 +9,8 @@ import org.apollo.game.model.area.update.GroupableEntity;
|
||||
import org.apollo.game.model.area.update.ItemUpdateOperation;
|
||||
import org.apollo.game.model.area.update.UpdateOperation;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* An {@link Item} displayed on the ground.
|
||||
*
|
||||
@@ -17,19 +19,19 @@ import org.apollo.game.model.area.update.UpdateOperation;
|
||||
public final class GroundItem extends Entity implements GroupableEntity {
|
||||
|
||||
/**
|
||||
* Creates a new GroundItem.
|
||||
* Creates a new <strong>global</strong> GroundItem.
|
||||
*
|
||||
* @param world The {@link World} containing the GroundItem.
|
||||
* @param position The {@link Position} of the Item.
|
||||
* @param item The Item displayed on the ground.
|
||||
* @return The GroundItem.
|
||||
*/
|
||||
public static GroundItem create(World world, Position position, Item item) {
|
||||
return new GroundItem(world, position, item, -1);
|
||||
public static GroundItem createGlobal(World world, Position position, Item item) {
|
||||
return new GroundItem(world, position, item, -1, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new dropped GroundItem.
|
||||
* Creates a new <strong>non-global</strong> dropped GroundItem.
|
||||
*
|
||||
* @param world The {@link World} containing the GroundItem.
|
||||
* @param position The {@link Position} of the Item.
|
||||
@@ -38,7 +40,7 @@ public final class GroundItem extends Entity implements GroupableEntity {
|
||||
* @return The GroundItem.
|
||||
*/
|
||||
public static GroundItem dropped(World world, Position position, Item item, Player owner) {
|
||||
return new GroundItem(world, position, item, owner.getIndex());
|
||||
return new GroundItem(world, position, item, owner.getIndex(), false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -51,6 +53,16 @@ public final class GroundItem extends Entity implements GroupableEntity {
|
||||
*/
|
||||
private final Item item;
|
||||
|
||||
/**
|
||||
* Represents the global state of this GroundItem.
|
||||
*/
|
||||
private boolean global;
|
||||
|
||||
/**
|
||||
* Represents the availability state of this GroundItem.
|
||||
*/
|
||||
private boolean available = true;
|
||||
|
||||
/**
|
||||
* Creates the GroundItem.
|
||||
*
|
||||
@@ -58,18 +70,20 @@ public final class GroundItem extends Entity implements GroupableEntity {
|
||||
* @param position The {@link Position} of the GroundItem.
|
||||
* @param item The Item displayed on the ground.
|
||||
* @param index The index of the {@link Player} who dropped this GroundItem.
|
||||
* @param global The global state of this GroundItem.
|
||||
*/
|
||||
private GroundItem(World world, Position position, Item item, int index) {
|
||||
private GroundItem(World world, Position position, Item item, int index, boolean global) {
|
||||
super(world, position);
|
||||
this.item = item;
|
||||
this.index = index;
|
||||
this.global = global;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof GroundItem) {
|
||||
GroundItem other = (GroundItem) obj;
|
||||
return position.equals(other.position);
|
||||
return item.equals(other.item) && position.equals(other.position) && global == other.global;
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -99,9 +113,48 @@ public final class GroundItem extends Entity implements GroupableEntity {
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the global state of this GroundItem.
|
||||
*
|
||||
* @return {@code true} iff this GroundItem is global.
|
||||
*/
|
||||
public boolean isGlobal() {
|
||||
return global;
|
||||
}
|
||||
|
||||
/**
|
||||
* Globalizes this GroundItem.
|
||||
*
|
||||
* @throws IllegalStateException If this GroundItem is already global.
|
||||
*/
|
||||
public void globalize() {
|
||||
if (global) {
|
||||
throw new IllegalStateException("Ground item state has already been set to global.");
|
||||
}
|
||||
global = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the availability of this GroundItem.
|
||||
*
|
||||
* @return {@code true} iff this GroundItem is available to be picked up.
|
||||
*/
|
||||
public boolean isAvailable() {
|
||||
return available;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether or not this GroundItem is available.
|
||||
*
|
||||
* @param available {@code true} if this GroundItem is available to be picked up, otherwise {@code false}.
|
||||
*/
|
||||
public void setAvailable(boolean available) {
|
||||
this.available = available;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return position.hashCode() * 31 + item.hashCode();
|
||||
return Objects.hash(item, position, global);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user