diff --git a/src/org/apollo/game/model/World.java b/src/org/apollo/game/model/World.java index f4ccaddc..4718a47d 100644 --- a/src/org/apollo/game/model/World.java +++ b/src/org/apollo/game/model/World.java @@ -385,12 +385,12 @@ public final class World { } /** - * Adds entities to regions in the {@link RegionRepository}. + * Adds entities to regions in the {@link RegionRepository}. By default, we do not notify listeners. * * @param entities The entities. */ private void placeEntities(Entity... entities) { - Arrays.stream(entities).forEach(entity -> regions.fromPosition(entity.getPosition()).addEntity(entity)); + Arrays.stream(entities).forEach(entity -> regions.fromPosition(entity.getPosition()).addEntity(entity, false)); } } \ No newline at end of file diff --git a/src/org/apollo/game/model/area/Region.java b/src/org/apollo/game/model/area/Region.java index 1a7c3662..e4a80263 100644 --- a/src/org/apollo/game/model/area/Region.java +++ b/src/org/apollo/game/model/area/Region.java @@ -31,280 +31,302 @@ import com.google.common.collect.ImmutableSet; */ public final class Region { - /** - * A {@link RegionListener} for {@link UpdateOperation}s. - * - * @author Major - */ - private static final class UpdateRegionListener implements RegionListener { + /** + * A {@link RegionListener} for {@link UpdateOperation}s. + * + * @author Major + */ + private static final class UpdateRegionListener implements RegionListener { - @Override - public void execute(Region region, Entity entity, EntityUpdateType update) { - EntityType type = entity.getEntityType(); - if (type != EntityType.PLAYER && type != EntityType.NPC && (type != EntityType.STATIC_OBJECT || update == EntityUpdateType.REMOVE)) { - region.record(entity, update); - } - } + @Override + public void execute(Region region, Entity entity, EntityUpdateType update) { + EntityType type = entity.getEntityType(); + if (type != EntityType.PLAYER && type != EntityType.NPC) { + region.record(entity, update); + } + } - } + } - /** - * The width and length of a Region, in tiles. - */ - public static final int SIZE = 8; + /** + * The width and length of a Region, in tiles. + */ + public static final int SIZE = 8; - static final long start = System.currentTimeMillis(); + static final long start = System.currentTimeMillis(); - /** - * The default size of newly-created sets, to reduce memory usage. - */ - private static final int DEFAULT_SET_SIZE = 2; + /** + * The default size of newly-created sets, to reduce memory usage. + */ + private static final int DEFAULT_SET_SIZE = 2; - /** - * The RegionCoordinates of this Region. - */ - private final RegionCoordinates coordinates; + /** + * The RegionCoordinates of this Region. + */ + private final RegionCoordinates coordinates; - /** - * The Map of Positions to Entities in that Position. - */ - private final Map> entities = new HashMap<>(); + /** + * The Map of Positions to Entities in that Position. + */ + private final Map> entities = new HashMap<>(); - /** - * A List of RegionListeners registered to this Region. - */ - private final List listeners = new ArrayList<>(); + /** + * A List of RegionListeners registered to this Region. + */ + private final List listeners = new ArrayList<>(); - /** - * The CollisionMatrix. - */ - private final CollisionMatrix[] matrices = CollisionMatrix.createMatrices(Position.HEIGHT_LEVELS, SIZE, SIZE); + /** + * The CollisionMatrix. + */ + private final CollisionMatrix[] matrices = CollisionMatrix.createMatrices(Position.HEIGHT_LEVELS, SIZE, SIZE); - /** - * The Set containing RegionUpdateMessages which can be sent to add every non-Mob Entity in this Region. - */ - private final List> snapshots = new ArrayList<>(Position.HEIGHT_LEVELS); + /** + * The Set containing RegionUpdateMessages which can be sent to add every non-Mob Entity in this Region. + */ + private final List> snapshots = new ArrayList<>(Position.HEIGHT_LEVELS); - /** - * The Set containing UpdateOperations. - */ - private final List> updates = new ArrayList<>(Position.HEIGHT_LEVELS); + /** + * The Set containing UpdateOperations. + */ + private final List> updates = new ArrayList<>(Position.HEIGHT_LEVELS); - /** - * Creates a new Region. - * - * @param x The x coordinate of the Region. - * @param y The y coordinate of the Region. - */ - public Region(int x, int y) { - this(new RegionCoordinates(x, y)); - } + /** + * Creates a new Region. + * + * @param x The x coordinate of the Region. + * @param y The y coordinate of the Region. + */ + public Region(int x, int y) { + this(new RegionCoordinates(x, y)); + } - /** - * Creates a new Region with the specified {@link RegionCoordinates}. - * - * @param coordinates The coordinates. - */ - public Region(RegionCoordinates coordinates) { - this.coordinates = coordinates; - listeners.add(new UpdateRegionListener()); + /** + * Creates a new Region with the specified {@link RegionCoordinates}. + * + * @param coordinates The coordinates. + */ + public Region(RegionCoordinates coordinates) { + this.coordinates = coordinates; + listeners.add(new UpdateRegionListener()); - for (int height = 0; height < Position.HEIGHT_LEVELS; height++) { - snapshots.add(new ArrayList<>()); - updates.add(new ArrayList<>(DEFAULT_SET_SIZE)); - } - } + for (int height = 0; height < Position.HEIGHT_LEVELS; height++) { + snapshots.add(new HashMap<>()); + updates.add(new ArrayList<>(DEFAULT_SET_SIZE)); + } + } - /** - * Adds a {@link Entity} from to Region. Note that this does not spawn the Entity, or do any other action other than - * register it to this Region. - * - * @param entity The Entity. - * @throws IllegalArgumentException If the Entity does not belong in this Region. - */ - public void addEntity(Entity entity) { - Position position = entity.getPosition(); - checkPosition(position); + /** + * Adds a {@link Entity} to the Region. Note that this does not spawn the Entity, or do any other action other than + * register it to this Region. + * + * @param entity The Entity. + * @param notify A flag indicating whether the {@link RegionListener}s for this Region should be notified. + * @throws IllegalArgumentException If the Entity does not belong in this Region. + */ + public void addEntity(Entity entity, boolean notify) { + Position position = entity.getPosition(); + checkPosition(position); - Set local = entities.computeIfAbsent(position, key -> new HashSet<>(DEFAULT_SET_SIZE)); - local.add(entity); + Set local = entities.computeIfAbsent(position, key -> new HashSet<>(DEFAULT_SET_SIZE)); + local.add(entity); - if ((System.currentTimeMillis() - start) / 1000 > 10 && entity instanceof GameObject) { - System.out.println("Adding entity " + entity + " to " + entity.getPosition()); - } + if ((System.currentTimeMillis() - start) / 1000 > 10 && (entity instanceof GameObject)) { + System.out.println("Adding entity " + entity + " to " + entity.getPosition()); + } - notifyListeners(entity, EntityUpdateType.ADD); - } + if (notify) { + notifyListeners(entity, EntityUpdateType.ADD); + } + } - /** - * Checks if this Region contains the specified Entity. - *

- * This method operates in constant time. - * - * @param entity The Entity. - * @return {@code true} if this Region contains the Entity, otherwise {@code false}. - */ - public boolean contains(Entity entity) { - Position position = entity.getPosition(); - Set local = entities.get(position); + /** + * Adds a {@link Entity} to the Region. Note that this does not spawn the Entity, or do any other action other than + * register it to this Region. + *

+ * By default, this method notifies RegionListeners for this region of the addition. + * + * @param entity The Entity. + * @throws IllegalArgumentException If the Entity does not belong in this Region. + */ + public void addEntity(Entity entity) { + addEntity(entity, true); + } - return local != null && local.contains(entity); - } + /** + * Checks if this Region contains the specified Entity. + *

+ * This method operates in constant time. + * + * @param entity The Entity. + * @return {@code true} if this Region contains the Entity, otherwise {@code false}. + */ - /** - * Gets this Region's {@link RegionCoordinates}. - * - * @return The Region coordinates. - */ - public RegionCoordinates getCoordinates() { - return coordinates; - } + public boolean contains(Entity entity) { + Position position = entity.getPosition(); + Set local = entities.get(position); - /** - * Gets a shallow copy of the {@link Set} of {@link Entity} objects at the specified {@link Position}. The returned - * type will be immutable. - * - * @param position The position containing the entities. - * @return The list. - */ - public Set getEntities(Position position) { - Set set = entities.get(position); - return set == null ? ImmutableSet.of() : ImmutableSet.copyOf(set); - } + return local != null && local.contains(entity); + } - /** - * Gets a shallow copy of the {@link Set} of {@link Entity}s with the specified {@link EntityType}(s). The returned - * type will be immutable. Type will be inferred from the call, so ensure that the Entity type and the reference - * correspond, or this method will fail at runtime. - * - * @param position The {@link Position} containing the entities. - * @param types The {@link EntityType}s. - * @return The set of entities. - */ - public Set getEntities(Position position, EntityType... types) { - Set local = entities.get(position); - if (local == null) { - return ImmutableSet.of(); - } + /** + * Gets this Region's {@link RegionCoordinates}. + * + * @return The Region coordinates. + */ + public RegionCoordinates getCoordinates() { + return coordinates; + } - Set set = new HashSet<>(Arrays.asList(types)); - @SuppressWarnings("unchecked") - Set filtered = (Set) local.stream().filter(entity -> set.contains(entity.getEntityType())).collect(Collectors.toSet()); - return ImmutableSet.copyOf(filtered); - } + /** + * Gets a shallow copy of the {@link Set} of {@link Entity} objects at the specified {@link Position}. The returned + * type will be immutable. + * + * @param position The position containing the entities. + * @return The list. + */ + public Set getEntities(Position position) { + Set set = entities.get(position); + return (set == null) ? ImmutableSet.of() : ImmutableSet.copyOf(set); + } - /** - * Gets the {@link CollisionMatrix} at the specified height level. - * - * @param height The height level. - * @return The CollisionMatrix. - */ - public CollisionMatrix getMatrix(int height) { - Preconditions.checkElementIndex(height, matrices.length, "Matrix height level must be [0, " + matrices.length + ")."); - return matrices[height]; - } + /** + * Gets a shallow copy of the {@link Set} of {@link Entity}s with the specified {@link EntityType}(s). The returned + * type will be immutable. Type will be inferred from the call, so ensure that the Entity type and the reference + * correspond, or this method will fail at runtime. + * + * @param position The {@link Position} containing the entities. + * @param types The {@link EntityType}s. + * @return The set of entities. + */ + public Set getEntities(Position position, EntityType... types) { + Set local = entities.get(position); + if (local == null) { + return ImmutableSet.of(); + } - /** - * Gets a {@link Set} containing {@link RegionUpdateMessage}s that add every {@link Entity} in this Region. - * - * @param height The height level to get the Set of RegionUpdateMessages for. - * @return The Set of RegionUpdateMessages. - */ - public List getSnapshot(int height) { - List copy = new ArrayList<>(snapshots.get(height)); - Collections.sort(copy); - return ImmutableList.copyOf(copy); - } + Set set = new HashSet<>(Arrays.asList(types)); + @SuppressWarnings("unchecked") + Set filtered = (Set) local.stream().filter(entity -> set.contains(entity.getEntityType())).collect(Collectors.toSet()); + return ImmutableSet.copyOf(filtered); + } - /** - * Gets the updates that have occurred in the last tick in this Region, as a {@link Set} of - * {@link RegionUpdateMessage}s. - * - * @param height The height level to get the Set of RegionUpdateMessages for. - * @return The Set of RegionUpdateMessages. - */ - public List getUpdates(int height) { - List original = updates.get(height); - List updates = new ArrayList<>(original); - original.clear(); + /** + * Gets the {@link CollisionMatrix} at the specified height level. + * + * @param height The height level. + * @return The CollisionMatrix. + */ + public CollisionMatrix getMatrix(int height) { + Preconditions.checkElementIndex(height, matrices.length, "Matrix height level must be [0, " + matrices.length + ")."); + return matrices[height]; + } - Collections.sort(updates); - return ImmutableList.copyOf(updates); - } + /** + * Gets a {@link Set} containing {@link RegionUpdateMessage}s that add every {@link Entity} in this Region. + * + * @param height The height level to get the Set of RegionUpdateMessages for. + * @return The Set of RegionUpdateMessages. + */ + public List getSnapshot(int height) { + List copy = new ArrayList<>(snapshots.get(height).values()); + Collections.sort(copy); + return ImmutableList.copyOf(copy); + } - /** - * Notifies the {@link RegionListener}s registered to this Region that an update has occurred. - * - * @param entity The {@link Entity} that was updated. - * @param type The {@link EntityUpdateType} that occurred. - */ - public void notifyListeners(Entity entity, EntityUpdateType type) { - listeners.forEach(listener -> listener.execute(this, entity, type)); - } + /** + * Gets the updates that have occurred in the last tick in this Region, as a {@link Set} of + * {@link RegionUpdateMessage}s. + * + * @param height The height level to get the Set of RegionUpdateMessages for. + * @return The Set of RegionUpdateMessages. + */ + public List getUpdates(int height) { + List original = this.updates.get(height); + List updates = new ArrayList<>(original); + original.clear(); - /** - * Removes a {@link Entity} from this Region. - * - * @param entity The Entity. - * @throws IllegalArgumentException If the Entity does not belong in this Region, or if it was never added. - */ - public void removeEntity(Entity entity) { - Position position = entity.getPosition(); - checkPosition(position); + Collections.sort(updates); + return ImmutableList.copyOf(updates); + } - Set local = entities.get(position); + /** + * Notifies the {@link RegionListener}s registered to this Region that an update has occurred. + * + * @param entity The {@link Entity} that was updated. + * @param type The {@link EntityUpdateType} that occurred. + */ + public void notifyListeners(Entity entity, EntityUpdateType type) { + listeners.forEach(listener -> listener.execute(this, entity, type)); + } - if (local == null || !local.remove(entity)) { - throw new IllegalArgumentException("Entity belongs in this Region (" + this + ") but does not exist."); - } + /** + * Removes a {@link Entity} from this Region. + * + * @param entity The Entity. + * @throws IllegalArgumentException If the Entity does not belong in this Region, or if it was never added. + */ + public void removeEntity(Entity entity) { + Position position = entity.getPosition(); + checkPosition(position); - notifyListeners(entity, EntityUpdateType.REMOVE); - } + Set local = entities.get(position); - @Override - public String toString() { - return MoreObjects.toStringHelper(this).add("coordinates", coordinates).toString(); - } + if (local == null || !local.remove(entity)) { + throw new IllegalArgumentException("Entity belongs in this Region (" + this + ") but does not exist."); + } - /** - * Returns whether or not an Entity of the specified {@link EntityType type} can traverse the tile at the specified - * coordinate pair. - * - * @param position The {@link Position} of the tile. - * @param entity The {@link EntityType}. - * @param direction The {@link Direction} the Entity is approaching from. - * @return {@code true} if the tile at the specified coordinate pair is traversable, {@code false} if not. - */ - public boolean traversable(Position position, EntityType entity, Direction direction) { - CollisionMatrix matrix = matrices[position.getHeight()]; - int x = position.getX(), y = position.getY(); + notifyListeners(entity, EntityUpdateType.REMOVE); + } - return !matrix.untraversable(x % SIZE, y % SIZE, entity, direction); - } + @Override + public String toString() { + return MoreObjects.toStringHelper(this).add("coordinates", coordinates).toString(); + } - /** - * Checks that the specified {@link Position} is included in this Region. - * - * @param position The position. - * @throws IllegalArgumentException If the specified position is not included in this Region. - */ - private void checkPosition(Position position) { - Preconditions.checkArgument(coordinates.equals(RegionCoordinates.fromPosition(position)), "Position is not included in this Region."); - } + /** + * Returns whether or not an Entity of the specified {@link EntityType type} can traverse the tile at the specified + * coordinate pair. + * + * @param position The {@link Position} of the tile. + * @param entity The {@link EntityType}. + * @param direction The {@link Direction} the Entity is approaching from. + * @return {@code true} if the tile at the specified coordinate pair is traversable, {@code false} if not. + */ + public boolean traversable(Position position, EntityType entity, Direction direction) { + CollisionMatrix matrix = matrices[position.getHeight()]; + int x = position.getX(), y = position.getY(); - /** - * Records the specified {@link Entity} as being updated this pulse. - * - * @param entity The Entity. - * @param type The {@link EntityUpdateType}. - * @throws UnsupportedOperationException If the specified Entity cannot be operated on in this manner. - */ - private void record(Entity entity, EntityUpdateType type) { - RegionUpdateMessage message = entity.toUpdateOperation(this, type).toMessage(); - int height = entity.getPosition().getHeight(); + return !matrix.untraversable(x % SIZE, y % SIZE, entity, direction); + } - updates.get(height).add(message); - snapshots.get(height).add(message); - } + /** + * Checks that the specified {@link Position} is included in this Region. + * + * @param position The position. + * @throws IllegalArgumentException If the specified position is not included in this Region. + */ + private void checkPosition(Position position) { + Preconditions.checkArgument(coordinates.equals(RegionCoordinates.fromPosition(position)), "Position is not included in this Region."); + } + + /** + * Records the specified {@link Entity} as being updated this pulse. + * + * @param entity The Entity. + * @param type The {@link EntityUpdateType}. + * @throws UnsupportedOperationException If the specified Entity cannot be operated on in this manner. + */ + private void record(Entity entity, EntityUpdateType type) { + RegionUpdateMessage message = entity.toUpdateOperation(this, type).toMessage(); + int height = entity.getPosition().getHeight(); + + updates.get(height).add(message); + snapshots.get(height).remove(entity); + + if ((entity.getEntityType() == EntityType.STATIC_OBJECT && type == EntityUpdateType.REMOVE) || + (entity.getEntityType() != EntityType.STATIC_OBJECT && type == EntityUpdateType.ADD)) { + snapshots.get(height).put(entity, message); + } + } } \ No newline at end of file