diff --git a/src/org/apollo/game/message/handler/impl/ObjectActionVerificationHandler.java b/src/org/apollo/game/message/handler/impl/ObjectActionVerificationHandler.java index e22a67c0..e1cf9b5e 100644 --- a/src/org/apollo/game/message/handler/impl/ObjectActionVerificationHandler.java +++ b/src/org/apollo/game/message/handler/impl/ObjectActionVerificationHandler.java @@ -1,6 +1,7 @@ package org.apollo.game.message.handler.impl; import java.util.List; +import java.util.Set; import org.apollo.game.message.handler.MessageHandler; import org.apollo.game.message.handler.MessageHandlerContext; @@ -36,14 +37,9 @@ public final class ObjectActionVerificationHandler extends MessageHandler objects = sector.getEntities(position, EntityType.GAME_OBJECT); + Set objects = sector.getEntities(position, EntityType.GAME_OBJECT); - if (!containsObject(id, objects)) { - ctx.breakHandlerChain(); - return; - } - - if (!player.getPosition().isWithinDistance(position, 15)) { + if (!player.getPosition().isWithinDistance(position, 15) || !containsObject(id, objects)) { ctx.breakHandlerChain(); return; } @@ -62,8 +58,8 @@ public final class ObjectActionVerificationHandler extends MessageHandler objects) { - return objects.stream().filter(object -> object.getId() == id).findAny().isPresent(); + private static boolean containsObject(int id, Set objects) { + return objects.stream().anyMatch(object -> object.getId() == id); } } \ No newline at end of file diff --git a/src/org/apollo/game/model/Position.java b/src/org/apollo/game/model/Position.java index 64783f0d..24c1e3de 100644 --- a/src/org/apollo/game/model/Position.java +++ b/src/org/apollo/game/model/Position.java @@ -1,5 +1,8 @@ package org.apollo.game.model; +import org.apollo.game.model.area.Sector; +import org.apollo.game.model.area.SectorCoordinates; + import com.google.common.base.MoreObjects; import com.google.common.base.Preconditions; @@ -23,7 +26,7 @@ public final class Position { /** * The packed integer containing the {@code height, x}, and {@code y} variables. */ - private final int position; + private final int packed; /** * Creates a position at the default height. @@ -45,14 +48,14 @@ public final class Position { public Position(int x, int y, int height) { Preconditions.checkArgument(height >= 0 && height < HEIGHT_LEVELS, "Height level out of bounds."); - position = height << 30 | (y & 0x7FFF) << 15 | x & 0x7FFF; + packed = height << 30 | (y & 0x7FFF) << 15 | x & 0x7FFF; } @Override public boolean equals(Object obj) { if (obj instanceof Position) { Position other = (Position) obj; - return position == other.position; + return packed == other.packed; } return false; @@ -94,7 +97,7 @@ public final class Position { * @return The height level. */ public int getHeight() { - return position >> 30; + return packed >> 30; } /** @@ -147,6 +150,15 @@ public final class Position { return Math.max(deltaX, deltaY); } + /** + * Returns the {@link SectorCoordinates} of the {@link Sector} this position is inside. + * + * @return The sector coordinates. + */ + public SectorCoordinates getSectorCoordinates() { + return SectorCoordinates.fromPosition(this); + } + /** * Gets the x coordinate of the sector this position is in. * @@ -171,7 +183,7 @@ public final class Position { * @return The x coordinate. */ public int getX() { - return position & 0x7FFF; + return packed & 0x7FFF; } /** @@ -180,12 +192,23 @@ public final class Position { * @return The y coordinate. */ public int getY() { - return (position >> 15) & 0x7FFF; + return (packed >> 15) & 0x7FFF; } @Override public int hashCode() { - return position; + return packed; + } + + /** + * Returns whether or not this position is inside the specified {@link Sector}. + * + * @param sector The sector. + * @return {@code true} if this position is inside the specified sector, otherwise {@code false}. + */ + public boolean inside(Sector sector) { + SectorCoordinates coordinates = sector.getCoordinates(); + return coordinates.equals(getSectorCoordinates()); } /** @@ -204,7 +227,7 @@ public final class Position { @Override public String toString() { return MoreObjects.toStringHelper(this).add("x", getX()).add("y", getY()).add("height", getHeight()) - .add("sector x", getTopLeftSectorX()).add("sector y", getTopLeftSectorY()).toString(); + .add("sector", getSectorCoordinates()).toString(); } } \ No newline at end of file diff --git a/src/org/apollo/game/model/World.java b/src/org/apollo/game/model/World.java index 2772272a..990a8a40 100644 --- a/src/org/apollo/game/model/World.java +++ b/src/org/apollo/game/model/World.java @@ -3,6 +3,7 @@ package org.apollo.game.model; import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.InputStream; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.logging.Logger; @@ -133,7 +134,7 @@ public final class World { /** * This world's {@link SectorRepository}. */ - private final SectorRepository sectorRepository = new SectorRepository(false); + private final SectorRepository sectors = SectorRepository.immutable(); /** * Creates the world. @@ -222,7 +223,7 @@ public final class World { * @return The sector repository. */ public SectorRepository getSectorRepository() { - return sectorRepository; + return sectors; } /** @@ -236,10 +237,10 @@ public final class World { public void init(int release, IndexedFileSystem fs, PluginManager manager) throws Exception { this.releaseNumber = release; - ItemDefinitionDecoder itemDefDecoder = new ItemDefinitionDecoder(fs); - ItemDefinition[] itemDefs = itemDefDecoder.decode(); - ItemDefinition.init(itemDefs); - logger.fine("Loaded " + itemDefs.length + " item definitions."); + ItemDefinitionDecoder itemDecoder = new ItemDefinitionDecoder(fs); + ItemDefinition[] items = itemDecoder.decode(); + ItemDefinition.init(items); + logger.fine("Loaded " + items.length + " item definitions."); try (InputStream is = new BufferedInputStream(new FileInputStream("data/equipment-" + release + ".dat"))) { EquipmentDefinitionParser parser = new EquipmentDefinitionParser(is); @@ -249,14 +250,14 @@ public final class World { } NpcDefinitionDecoder npcDecoder = new NpcDefinitionDecoder(fs); - NpcDefinition[] npcDefs = npcDecoder.decode(); - NpcDefinition.init(npcDefs); - logger.fine("Loaded " + npcDefs.length + " npc definitions."); + NpcDefinition[] npcs = npcDecoder.decode(); + NpcDefinition.init(npcs); + logger.fine("Loaded " + npcs.length + " npc definitions."); ObjectDefinitionDecoder objectDecoder = new ObjectDefinitionDecoder(fs); - ObjectDefinition[] objDefs = objectDecoder.decode(); - ObjectDefinition.init(objDefs); - logger.fine("Loaded " + objDefs.length + " object definitions."); + ObjectDefinition[] objectDefs = objectDecoder.decode(); + ObjectDefinition.init(objectDefs); + logger.fine("Loaded " + objectDefs.length + " object definitions."); GameObjectDecoder staticDecoder = new GameObjectDecoder(fs); GameObject[] objects = staticDecoder.decode(); @@ -283,10 +284,7 @@ public final class World { * @param entities The entities. */ private void placeEntities(Entity... entities) { - for (Entity entity : entities) { - Sector sector = sectorRepository.fromPosition(entity.getPosition()); - sector.addEntity(entity); - } + Arrays.stream(entities).forEach(entity -> sectors.fromPosition(entity.getPosition()).addEntity(entity)); } /** @@ -306,7 +304,7 @@ public final class World { boolean success = npcRepository.add(npc); if (success) { - Sector sector = sectorRepository.fromPosition(npc.getPosition()); + Sector sector = sectors.fromPosition(npc.getPosition()); sector.addEntity(npc); } else { logger.warning("Failed to register npc, repository capacity reached: [count=" + npcRepository.size() + "]"); @@ -329,7 +327,7 @@ public final class World { boolean success = playerRepository.add(player); if (success) { players.put(NameUtil.encodeBase37(username), player); - Sector sector = sectorRepository.fromPosition(player.getPosition()); + Sector sector = sectors.fromPosition(player.getPosition()); sector.addEntity(player); logger.info("Registered player: " + player + " [count=" + playerRepository.size() + "]"); @@ -357,11 +355,9 @@ public final class World { */ public void unregister(final Npc npc) { if (npcRepository.remove(npc)) { - Sector sector = sectorRepository.fromPosition(npc.getPosition()); + Sector sector = sectors.fromPosition(npc.getPosition()); - if (!sector.removeEntity(npc)) { - logger.warning("Could not remove npc from their sector."); - } + sector.removeEntity(npc); } else { logger.warning("Could not find npc " + npc + " to unregister!"); } @@ -377,10 +373,8 @@ public final class World { players.remove(NameUtil.encodeBase37(player.getUsername())); logger.info("Unregistered player: " + player + " [count=" + playerRepository.size() + "]"); - Sector sector = sectorRepository.fromPosition(player.getPosition()); - if (!sector.removeEntity(player)) { - logger.warning("Could not remove player from their sector."); - } + Sector sector = sectors.fromPosition(player.getPosition()); + sector.removeEntity(player); logoutDispatcher.dispatch(player); } else { diff --git a/src/org/apollo/game/model/area/Sector.java b/src/org/apollo/game/model/area/Sector.java index c028153b..7f9f63eb 100644 --- a/src/org/apollo/game/model/area/Sector.java +++ b/src/org/apollo/game/model/area/Sector.java @@ -2,15 +2,19 @@ package org.apollo.game.model.area; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; import org.apollo.game.model.Position; import org.apollo.game.model.entity.Entity; import org.apollo.game.model.entity.Entity.EntityType; +import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; /** * An 8x8 area of the map. @@ -19,6 +23,11 @@ import com.google.common.collect.ImmutableList; */ public final class Sector { + /** + * The default size of newly-created sets, to reduce memory usage. + */ + private static final int DEFAULT_SET_SIZE = 2; + /** * The width and length of a sector, in tiles. */ @@ -32,7 +41,7 @@ public final class Sector { /** * A map of positions to entities in that position. */ - private final Map> entities = new HashMap<>(); + private final Map> entities = new HashMap<>(); /** * A list of listeners registered to this sector. @@ -66,27 +75,37 @@ public final class Sector { */ public void addEntity(Entity entity) { Position position = entity.getPosition(); - List entities = this.entities.get(position); - if (entities == null) { - entities = new ArrayList<>(); - } + checkPosition(position); + Set local = entities.computeIfAbsent(position, key -> new HashSet<>(DEFAULT_SET_SIZE)); - entities.add(entity); - this.entities.put(position, entities); + local.add(entity); notifyListeners(entity, SectorOperation.ADD); } + /** + * Checks that the specified {@link Position} is included in this sector. + * + * @param position The position. + * @throws IllegalArgumentException If the specified position is not included in this sector. + */ + private void checkPosition(Position position) { + Preconditions.checkArgument(coordinates.equals(SectorCoordinates.fromPosition(position)), + "Position is not included in this sector."); + } + /** * Checks if this sector contains the specified entity. + *

+ * This method operates in constant time. * * @param entity The entity. * @return {@code true} if this sector contains the entity, otherwise {@code false}. */ public boolean contains(Entity entity) { Position position = entity.getPosition(); - List entities = this.entities.get(position); + Set local = entities.get(position); - return entities != null && entities.contains(entity); + return local != null && local.contains(entity); } /** @@ -98,17 +117,6 @@ public final class Sector { return coordinates; } - /** - * Gets an {@link ImmutableList} containing every {@link Entity} in this sector. - * - * @return The list. - */ - public List getEntities() { - List combined = new ArrayList<>(); - this.entities.values().forEach(entities -> combined.addAll(entities)); - return ImmutableList.copyOf(combined); - } - /** * Gets a shallow copy of the {@link List} of {@link Entity} objects at the specified {@link Position}. The returned * type will be {@link ImmutableList}. @@ -117,13 +125,7 @@ public final class Sector { * @return The list. */ public List getEntities(Position position) { - List entities = this.entities.get(position); - if (entities == null) { - this.entities.put(position, new ArrayList<>()); - return ImmutableList.of(); - } - - return ImmutableList.copyOf(entities); + return ImmutableList.copyOf(entities.computeIfAbsent(position, key -> new HashSet<>(DEFAULT_SET_SIZE))); } /** @@ -135,16 +137,12 @@ public final class Sector { * @param type The {@link EntityType}. * @return The list of entities. */ - public List getEntities(Position position, EntityType type) { - List entities = this.entities.get(position); - if (entities == null) { - this.entities.put(position, new ArrayList<>()); - return ImmutableList.of(); - } + public Set getEntities(Position position, EntityType type) { + Set local = entities.computeIfAbsent(position, key -> new HashSet<>(DEFAULT_SET_SIZE)); @SuppressWarnings("unchecked") - List filtered = (List) entities.stream().filter(e -> e.getEntityType() == type).collect(Collectors.toList()); - return ImmutableList.copyOf(filtered); + Set filtered = (Set) local.stream().filter(entity -> entity.getEntityType() == type).collect(Collectors.toSet()); + return ImmutableSet.copyOf(filtered); } /** @@ -161,17 +159,24 @@ public final class Sector { * Removes a {@link Entity} from this sector. * * @param entity The entity. - * @return {@code true} if the entity was removed, otherwise {@code false}. + * @throws IllegalArgumentException If the entity does not belong in this sector, or if it was never added. */ - public boolean removeEntity(Entity entity) { - Position position = entity.getPosition(); - List entities = this.entities.get(position); + public void removeEntity(Entity entity) { + try { + Position position = entity.getPosition(); + checkPosition(position); + + Set local = entities.get(position); + + if (local == null || !local.remove(entity)) { + throw new IllegalArgumentException("Entity belongs in this sector but does not exist."); + } - if (entities != null && entities.remove(entity)) { notifyListeners(entity, SectorOperation.REMOVE); - return true; + } catch (Exception e) { + e.printStackTrace(); + throw e; } - return false; } } \ No newline at end of file diff --git a/src/org/apollo/game/model/area/SectorCoordinates.java b/src/org/apollo/game/model/area/SectorCoordinates.java index d79ad183..99d58098 100644 --- a/src/org/apollo/game/model/area/SectorCoordinates.java +++ b/src/org/apollo/game/model/area/SectorCoordinates.java @@ -2,11 +2,14 @@ package org.apollo.game.model.area; import org.apollo.game.model.Position; +import com.google.common.base.MoreObjects; + /** * An immutable class representing the coordinates of a sector, where the coordinates ({@code x, y}) are the top-left of * the sector. * * @author Graham + * @author Major */ public final class SectorCoordinates { @@ -43,12 +46,12 @@ public final class SectorCoordinates { @Override public boolean equals(Object obj) { - if (obj == null || getClass() != obj.getClass()) { - return false; + if (obj instanceof SectorCoordinates) { + SectorCoordinates other = (SectorCoordinates) obj; + return x == other.x && y == other.y; } - final SectorCoordinates other = (SectorCoordinates) obj; - return x == other.x && y == other.y; + return false; } /** @@ -74,4 +77,9 @@ public final class SectorCoordinates { return x << 16 | y; } + @Override + public String toString() { + return MoreObjects.toStringHelper(this).add("x", x).add("y", y).toString(); + } + } \ No newline at end of file diff --git a/src/org/apollo/game/model/area/SectorRepository.java b/src/org/apollo/game/model/area/SectorRepository.java index aa9cadf8..9e9b5188 100644 --- a/src/org/apollo/game/model/area/SectorRepository.java +++ b/src/org/apollo/game/model/area/SectorRepository.java @@ -17,6 +17,27 @@ import com.google.common.collect.ImmutableList; */ public final class SectorRepository { + /** + * Returns an immutable sector repository, where {@link Sector}s cannot be added or removed. + *

+ * Note that, internally, sectors are added lazily (i.e. only when necessary). As such, repositories are (again, + * internally) not actually immutable, so do not rely on such behaviour. + * + * @return The sector repository. + */ + public static SectorRepository immutable() { + return new SectorRepository(false); + } + + /** + * Returns a mutable sector repository, where {@link Sector}s may be removed. + * + * @return The sector repository. + */ + public static SectorRepository mutable() { + return new SectorRepository(true); + } + /** * Whether or not sectors can be removed from this repository. */ @@ -32,7 +53,7 @@ public final class SectorRepository { * * @param permitRemoval If removal (of {@link Sector}s) from this repository should be permitted. */ - public SectorRepository(boolean permitRemoval) { + private SectorRepository(boolean permitRemoval) { this.permitRemoval = permitRemoval; } @@ -44,11 +65,12 @@ public final class SectorRepository { * @throws UnsupportedOperationException If the coordinates of the provided sector are already mapped (and hence the * existing sector would be replaced), and removal of sectors is not permitted. */ - public void add(Sector sector) { + private void add(Sector sector) { Preconditions.checkNotNull(sector, "Sector cannot be null."); if (sectors.containsKey(sector.getCoordinates()) && !permitRemoval) { throw new UnsupportedOperationException("Cannot add a sector with the same coordinates as an existing sector."); } + sectors.put(sector.getCoordinates(), sector); } @@ -97,6 +119,7 @@ public final class SectorRepository { sector = new Sector(coordinates); add(sector); } + return sector; } @@ -121,6 +144,7 @@ public final class SectorRepository { if (!permitRemoval) { throw new UnsupportedOperationException("Cannot remove sectors from this repository."); } + return sectors.remove(sector.getCoordinates()) != null; } diff --git a/src/org/apollo/game/model/entity/Entity.java b/src/org/apollo/game/model/entity/Entity.java index cf381d18..bfbd65cd 100644 --- a/src/org/apollo/game/model/entity/Entity.java +++ b/src/org/apollo/game/model/entity/Entity.java @@ -73,4 +73,10 @@ public abstract class Entity { return position; } + @Override + public abstract boolean equals(Object obj); + + @Override + public abstract int hashCode(); + } \ No newline at end of file diff --git a/src/org/apollo/game/model/entity/GameObject.java b/src/org/apollo/game/model/entity/GameObject.java index 3d27b8a0..36b596a2 100644 --- a/src/org/apollo/game/model/entity/GameObject.java +++ b/src/org/apollo/game/model/entity/GameObject.java @@ -31,6 +31,16 @@ public final class GameObject extends Entity { this.packed = id << 8 | type << 2 | orientation; } + @Override + public boolean equals(Object obj) { + if (obj instanceof GameObject) { + GameObject other = (GameObject) obj; + return position.equals(other.position) && packed == other.packed; + } + + return false; + } + /** * Gets the definition of this object. * @@ -72,6 +82,11 @@ public final class GameObject extends Entity { return (packed >> 2) & 0x3F; } + @Override + public int hashCode() { + return packed; + } + @Override public String toString() { return MoreObjects.toStringHelper(this).add("id", getId()).add("type", getType()).add("rotation", getRotation()) diff --git a/src/org/apollo/game/model/entity/Mob.java b/src/org/apollo/game/model/entity/Mob.java index 61bee841..ad4c6512 100644 --- a/src/org/apollo/game/model/entity/Mob.java +++ b/src/org/apollo/game/model/entity/Mob.java @@ -3,6 +3,7 @@ package org.apollo.game.model.entity; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Optional; import org.apollo.game.action.Action; import org.apollo.game.model.Animation; @@ -11,7 +12,6 @@ import org.apollo.game.model.Graphic; import org.apollo.game.model.Position; import org.apollo.game.model.World; import org.apollo.game.model.area.Sector; -import org.apollo.game.model.area.SectorCoordinates; import org.apollo.game.model.area.SectorRepository; import org.apollo.game.model.def.NpcDefinition; import org.apollo.game.model.entity.attr.Attribute; @@ -20,6 +20,7 @@ import org.apollo.game.model.inv.Inventory; import org.apollo.game.model.inv.Inventory.StackMode; import org.apollo.game.model.inv.InventoryConstants; import org.apollo.game.scheduling.impl.SkillNormalizationTask; +import org.apollo.game.sync.block.InteractingMobBlock; import org.apollo.game.sync.block.SynchronizationBlock; import org.apollo.game.sync.block.SynchronizationBlockSet; @@ -31,11 +32,6 @@ import org.apollo.game.sync.block.SynchronizationBlockSet; */ public abstract class Mob extends Entity { - /** - * This mob's current action. - */ - private transient Action action; - /** * The attribute map of this mob. */ @@ -49,23 +45,13 @@ public abstract class Mob extends Entity { /** * This mob's npc definition. A player only uses this if they are appearing as an npc. */ - protected NpcDefinition definition; + protected Optional definition; /** * This mob's equipment. */ protected final Inventory equipment = new Inventory(InventoryConstants.EQUIPMENT_CAPACITY, StackMode.STACK_ALWAYS); - /** - * The position this mob is facing towards. - */ - private transient Position facingPosition = position; - - /** - * This mob's first movement direction. - */ - private transient Direction firstDirection = Direction.NONE; - /** * The index of this mob. */ @@ -81,6 +67,31 @@ public abstract class Mob extends Entity { */ protected final Inventory inventory = new Inventory(InventoryConstants.INVENTORY_CAPACITY); + /** + * This mob's skill set. + */ + protected final SkillSet skillSet = new SkillSet(); + + /** + * This mob's walking queue. + */ + protected final transient WalkingQueue walkingQueue = new WalkingQueue(this); + + /** + * This mob's current action. + */ + private transient Action action; + + /** + * The position this mob is facing towards. + */ + private transient Position facingPosition = position; + + /** + * This mob's first movement direction. + */ + private transient Direction firstDirection = Direction.NONE; + /** * This mob's list of local npcs. */ @@ -96,28 +107,30 @@ public abstract class Mob extends Entity { */ private transient Direction secondDirection = Direction.NONE; - /** - * This mob's skill set. - */ - protected final SkillSet skillSet = new SkillSet(); - /** * Indicates whether this mob is currently teleporting or not. */ private transient boolean teleporting = false; /** - * This mob's walking queue. - */ - protected final transient WalkingQueue walkingQueue = new WalkingQueue(this); - - /** - * Creates a new mob with the specified initial {@link Position}. - * - * @param position The initial position. + * Creates the mob with the specified initial {@link Position}. + * + * @param position The position. */ public Mob(Position position) { + this(position, null); + } + + /** + * Creates the mob. + * + * @param position The initial position. + * @param definition The {@link NpcDefinition}. + */ + public Mob(Position position, NpcDefinition definition) { super(position); + this.definition = Optional.ofNullable(definition); + init(); } @@ -170,7 +183,7 @@ public abstract class Mob extends Entity { * @return The npc definition. */ public final NpcDefinition getDefinition() { - return definition; + return definition.get(); } /** @@ -183,6 +196,7 @@ public abstract class Mob extends Entity { return secondDirection == Direction.NONE ? new Direction[] { firstDirection } : new Direction[] { firstDirection, secondDirection }; } + return Direction.EMPTY_DIRECTION_ARRAY; } @@ -288,10 +302,12 @@ public abstract class Mob extends Entity { } /** - * Initialises this mob. + * Returns whether or not this mob has an {@link NpcDefinition}. + * + * @return {@code true} if this mob has an npc definition, {@code false} if not. */ - private void init() { - World.getWorld().schedule(new SkillNormalizationTask(this)); + public final boolean hasNpcDefinition() { + return definition.isPresent(); } /** @@ -342,7 +358,7 @@ public abstract class Mob extends Entity { */ public final void resetInteractingMob() { interactingMob = null; - blockSet.add(SynchronizationBlock.createInteractingMobBlock(65535)); + blockSet.add(SynchronizationBlock.createInteractingMobBlock(InteractingMobBlock.RESET_INDEX)); } /** @@ -358,10 +374,11 @@ public abstract class Mob extends Entity { /** * Sets this mob's {@link NpcDefinition}. * - * @param definition The definition. + * @param definition The definition. Must not be {@code null}. + * @throws NullPointerException If the specified definition is {@code null}. */ public final void setDefinition(NpcDefinition definition) { - this.definition = definition; + this.definition = Optional.of(definition); } /** @@ -403,17 +420,14 @@ public abstract class Mob extends Entity { */ public final void setPosition(Position position) { SectorRepository repository = World.getWorld().getSectorRepository(); - Sector newSector = repository.fromPosition(position); + Sector current = repository.fromPosition(this.position); - if (SectorCoordinates.fromPosition(this.position) != SectorCoordinates.fromPosition(position)) { - Sector oldSector = repository.fromPosition(this.position); - oldSector.removeEntity(this); - } else { - newSector.removeEntity(this); - } + Sector next = position.inside(current) ? current : repository.fromPosition(position); - this.position = position; - newSector.addEntity(this); + current.removeEntity(this); + this.position = position; // addEntity relies on the position being updated, so do that first. + + next.addEntity(this); } /** @@ -446,6 +460,7 @@ public abstract class Mob extends Entity { if (this.action.equals(action)) { return false; } + stopAction(); } @@ -500,4 +515,11 @@ public abstract class Mob extends Entity { blockSet.add(SynchronizationBlock.createTurnToPositionBlock(position)); } + /** + * Initialises this mob. + */ + private void init() { + World.getWorld().schedule(new SkillNormalizationTask(this)); + } + } \ No newline at end of file diff --git a/src/org/apollo/game/model/entity/Npc.java b/src/org/apollo/game/model/entity/Npc.java index 26b8e6e1..478b77a1 100644 --- a/src/org/apollo/game/model/entity/Npc.java +++ b/src/org/apollo/game/model/entity/Npc.java @@ -1,6 +1,11 @@ package org.apollo.game.model.entity; +import java.util.Arrays; +import java.util.Optional; + import org.apollo.game.model.Position; +import org.apollo.game.model.World; +import org.apollo.game.model.area.Sector; import org.apollo.game.model.def.NpcDefinition; import org.apollo.game.sync.block.SynchronizationBlock; @@ -14,11 +19,6 @@ import com.google.common.base.Preconditions; */ public final class Npc extends Mob { - /** - * This npc's id. - */ - private int id; - /** * The positions representing the bounds (i.e. walking limits) of this npc. */ @@ -31,19 +31,29 @@ public final class Npc extends Mob { * @param position The position. */ public Npc(int id, Position position) { - this(NpcDefinition.lookup(id), position); + this(position, NpcDefinition.lookup(id)); } /** * Creates a new npc with the specified {@link NpcDefinition} and {@link Position}. * - * @param definition The definition. * @param position The position. + * @param definition The definition. */ - public Npc(NpcDefinition definition, Position position) { - super(position); - this.definition = definition; - this.id = definition.getId(); + public Npc(Position position, NpcDefinition definition) { + super(position, definition); + + init(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof Npc) { + Npc other = (Npc) obj; + return position.equals(other.position) && Arrays.equals(boundary, other.boundary) && getId() == other.getId(); + } + + return false; } /** @@ -52,7 +62,7 @@ public final class Npc extends Mob { * @return The boundary. */ public Position[] getBoundary() { - return boundary; + return boundary.clone(); } @Override @@ -66,7 +76,14 @@ public final class Npc extends Mob { * @return The id. */ public int getId() { - return id; + return definition.get().getId(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = prime * position.hashCode() + Arrays.hashCode(boundary); + return prime * result + getId(); } /** @@ -85,12 +102,12 @@ public final class Npc extends Mob { */ public void setBoundary(Position[] boundary) { Preconditions.checkArgument(boundary.length == 4, "Boundary count must be 4."); - this.boundary = boundary; + this.boundary = boundary.clone(); } @Override public String toString() { - return MoreObjects.toStringHelper(this).add("id", definition.getId()).add("name", definition.getName()).toString(); + return MoreObjects.toStringHelper(this).add("id", getId()).add("name", definition.get().getName()).toString(); } /** @@ -99,9 +116,20 @@ public final class Npc extends Mob { * @param id The id. */ public void transform(int id) { - Preconditions.checkArgument(id >= 0 && id < NpcDefinition.count(), "Id to transform to is out of bounds."); - definition = NpcDefinition.lookup(this.id = id); + Preconditions.checkElementIndex(id, NpcDefinition.count(), "Id to transform to is out of bounds."); + + definition = Optional.of(NpcDefinition.lookup(id)); blockSet.add(SynchronizationBlock.createTransformBlock(id)); } + /** + * Initialises this npc. + */ + private void init() { + // This has to be here instead of in Mob#init because of ordering issues - the player cannot be added to the + // sector until their credentials have been set, which is only done after the super constructors are called. + Sector sector = World.getWorld().getSectorRepository().get(position.getSectorCoordinates()); + sector.addEntity(this); + } + } \ No newline at end of file diff --git a/src/org/apollo/game/model/entity/Player.java b/src/org/apollo/game/model/entity/Player.java index a479221c..2367991e 100644 --- a/src/org/apollo/game/model/entity/Player.java +++ b/src/org/apollo/game/model/entity/Player.java @@ -18,6 +18,7 @@ import org.apollo.game.message.impl.UpdateRunEnergyMessage; import org.apollo.game.model.Appearance; import org.apollo.game.model.Position; import org.apollo.game.model.World; +import org.apollo.game.model.area.Sector; import org.apollo.game.model.inter.InterfaceConstants; import org.apollo.game.model.inter.InterfaceListener; import org.apollo.game.model.inter.InterfaceSet; @@ -82,6 +83,21 @@ public final class Player extends Mob { */ private PlayerCredentials credentials; + @Override + public int hashCode() { + return credentials.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof Player) { + Player other = (Player) obj; + return credentials.equals(other.credentials); + } + + return false; + } + /** * A flag which indicates there are npcs that couldn't be added. */ @@ -206,6 +222,7 @@ public final class Player extends Mob { public Player(PlayerCredentials credentials, Position position) { super(position); this.credentials = credentials; + init(); } @@ -923,6 +940,11 @@ public final class Player extends Mob { private void init() { initInventories(); initSkills(); + + // This has to be here instead of in Mob#init because of ordering issues - the player cannot be added to the + // sector until their credentials have been set, which is only done after the super constructors are called. + Sector sector = World.getWorld().getSectorRepository().get(position.getSectorCoordinates()); + sector.addEntity(this); } /** diff --git a/src/org/apollo/game/sync/block/InteractingMobBlock.java b/src/org/apollo/game/sync/block/InteractingMobBlock.java index d5f2a7ba..23c646c9 100644 --- a/src/org/apollo/game/sync/block/InteractingMobBlock.java +++ b/src/org/apollo/game/sync/block/InteractingMobBlock.java @@ -7,18 +7,23 @@ package org.apollo.game.sync.block; */ public final class InteractingMobBlock extends SynchronizationBlock { + /** + * The index used to reset the interacting mob. + */ + public static final int RESET_INDEX = 65_535; + /** * The index of the mob. */ - private final int mobIndex; + private final int index; /** * Creates the interacting mob block. * - * @param mobIndex The index of the current interacting mob. + * @param index The index of the current interacting mob. */ - InteractingMobBlock(int mobIndex) { - this.mobIndex = mobIndex; + InteractingMobBlock(int index) { + this.index = index; } /** @@ -26,8 +31,8 @@ public final class InteractingMobBlock extends SynchronizationBlock { * * @return The index. */ - public int getInteractingMobIndex() { - return mobIndex; + public int getIndex() { + return index; } } \ No newline at end of file diff --git a/src/org/apollo/game/sync/block/SynchronizationBlock.java b/src/org/apollo/game/sync/block/SynchronizationBlock.java index c2440100..e40d8a1e 100644 --- a/src/org/apollo/game/sync/block/SynchronizationBlock.java +++ b/src/org/apollo/game/sync/block/SynchronizationBlock.java @@ -34,9 +34,11 @@ public abstract class SynchronizationBlock { * @return The appearance block. */ public static SynchronizationBlock createAppearanceBlock(Player player) { - return new AppearanceBlock(player.getEncodedName(), player.getAppearance(), player.getSkillSet().getCombatLevel(), 0, - player.getEquipment(), player.getPrayerIcon(), player.isSkulled(), player.getDefinition() == null ? -1 : player - .getDefinition().getId()); + int combat = player.getSkillSet().getCombatLevel(); + int id = player.hasNpcDefinition() ? player.getDefinition().getId() : -1; + + return new AppearanceBlock(player.getEncodedName(), player.getAppearance(), combat, 0, player.getEquipment(), + player.getPrayerIcon(), player.isSkulled(), id); } /** diff --git a/src/org/apollo/game/sync/task/PhasedSynchronizationTask.java b/src/org/apollo/game/sync/task/PhasedSynchronizationTask.java index 31ae0c61..020ea7c8 100644 --- a/src/org/apollo/game/sync/task/PhasedSynchronizationTask.java +++ b/src/org/apollo/game/sync/task/PhasedSynchronizationTask.java @@ -38,9 +38,12 @@ public final class PhasedSynchronizationTask extends SynchronizationTask { public void run() { try { task.run(); + } catch (Exception e) { // TODO better solution... + e.printStackTrace(); + // The executor suppresses any exceptions thrown as part of the task, so we catch and print here as + // rethrowing them does nothing. } finally { phaser.arriveAndDeregister(); } } - } \ No newline at end of file diff --git a/src/org/apollo/game/sync/task/PrePlayerSynchronizationTask.java b/src/org/apollo/game/sync/task/PrePlayerSynchronizationTask.java index 7ec99aaf..cf2a0512 100644 --- a/src/org/apollo/game/sync/task/PrePlayerSynchronizationTask.java +++ b/src/org/apollo/game/sync/task/PrePlayerSynchronizationTask.java @@ -53,7 +53,6 @@ public final class PrePlayerSynchronizationTask extends SynchronizationTask { Position position = player.getPosition(); player.setLastKnownSector(position); - player.send(new SectorChangeMessage(position)); } } diff --git a/src/org/apollo/net/release/r317/MouseClickMessageDecoder.java b/src/org/apollo/net/release/r317/MouseClickMessageDecoder.java index f39b6b7e..717d3b70 100644 --- a/src/org/apollo/net/release/r317/MouseClickMessageDecoder.java +++ b/src/org/apollo/net/release/r317/MouseClickMessageDecoder.java @@ -19,19 +19,21 @@ public final class MouseClickMessageDecoder extends MessageDecoder> 12); + int clicks = (read >> 12); int dX = (read >> 6) & 0x3f; int dY = read & 0x3f; - return new MouseClickMessage(clickCount, dX, dY, true); + return new MouseClickMessage(clicks, dX, dY, true); } else if (reader.getLength() == 3) { read = (int) reader.getUnsigned(DataType.TRI_BYTE) & ~0x800000; } else { read = (int) reader.getUnsigned(DataType.INT) & ~0xc0000000; } - int clickCount = (read >> 19); + + int clicks = (read >> 19); int x = (read & 0x7f) % 765; int y = (read & 0x7f) / 765; - return new MouseClickMessage(clickCount, x, y, false); + + return new MouseClickMessage(clicks, x, y, false); } } \ No newline at end of file diff --git a/src/org/apollo/net/release/r317/NpcSynchronizationMessageEncoder.java b/src/org/apollo/net/release/r317/NpcSynchronizationMessageEncoder.java index 89299192..bc2472df 100644 --- a/src/org/apollo/net/release/r317/NpcSynchronizationMessageEncoder.java +++ b/src/org/apollo/net/release/r317/NpcSynchronizationMessageEncoder.java @@ -217,7 +217,7 @@ public final class NpcSynchronizationMessageEncoder extends MessageEncoder> 12); + clicks = (read >> 12); x = (read >> 6) & 0x3f; y = read & 0x3f; - return new MouseClickMessage(clickCount, x, y, true); + return new MouseClickMessage(clicks, x, y, true); } else if (reader.getLength() == 3) { read = (int) reader.getUnsigned(DataType.TRI_BYTE) & ~0x800000; } else { read = (int) reader.getUnsigned(DataType.INT) & ~0xc0000000; } - clickCount = (read >> 19); + + clicks = (read >> 19); x = (read & 0x7f) % 765; y = (read & 0x7f) / 765; - return new MouseClickMessage(clickCount, x, y, false); + return new MouseClickMessage(clicks, x, y, false); } } \ No newline at end of file diff --git a/src/org/apollo/net/release/r377/NpcSynchronizationMessageEncoder.java b/src/org/apollo/net/release/r377/NpcSynchronizationMessageEncoder.java index 480957d5..c95abd0e 100644 --- a/src/org/apollo/net/release/r377/NpcSynchronizationMessageEncoder.java +++ b/src/org/apollo/net/release/r377/NpcSynchronizationMessageEncoder.java @@ -217,7 +217,7 @@ public final class NpcSynchronizationMessageEncoder extends MessageEncoder