Initial implementation of ground items

This commit is contained in:
Ryley Kimmel
2017-11-12 17:57:07 -05:00
parent 397d9b2d6a
commit a75fa743f8
8 changed files with 245 additions and 37 deletions
@@ -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