From 84373635a1f414069777f77a531b7343cfa358a0 Mon Sep 17 00:00:00 2001 From: Steve Date: Fri, 10 Apr 2015 07:00:36 -0400 Subject: [PATCH 1/3] Fix the re-addition of StaticObjects to the game world. --- src/org/apollo/game/model/World.java | 21 +- src/org/apollo/game/model/area/Region.java | 514 +++++++++++---------- 2 files changed, 273 insertions(+), 262 deletions(-) diff --git a/src/org/apollo/game/model/World.java b/src/org/apollo/game/model/World.java index e74d0fbc..ba0a0881 100644 --- a/src/org/apollo/game/model/World.java +++ b/src/org/apollo/game/model/World.java @@ -1,13 +1,6 @@ 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; - +import com.google.common.base.Preconditions; import org.apollo.Service; import org.apollo.fs.IndexedFileSystem; import org.apollo.fs.decoder.GameObjectDecoder; @@ -37,7 +30,13 @@ import org.apollo.util.MobRepository; import org.apollo.util.NameUtil; import org.apollo.util.plugin.PluginManager; -import com.google.common.base.Preconditions; +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; /** * The world class is a singleton which contains objects like the {@link MobRepository} for players and NPCs. It should @@ -385,12 +384,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 e5caff4b..838e560e 100644 --- a/src/org/apollo/game/model/area/Region.java +++ b/src/org/apollo/game/model/area/Region.java @@ -1,15 +1,9 @@ package org.apollo.game.model.area; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -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 com.google.common.base.MoreObjects; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import org.apollo.game.message.impl.RegionUpdateMessage; import org.apollo.game.model.Direction; import org.apollo.game.model.Position; @@ -19,292 +13,310 @@ import org.apollo.game.model.entity.Entity; import org.apollo.game.model.entity.Entity.EntityType; import org.apollo.game.model.entity.obj.GameObject; -import com.google.common.base.MoreObjects; -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; +import java.util.*; +import java.util.stream.Collectors; /** * An 8x8 area of the map. - * + * * @author Major */ 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 ArrayList<>()); + 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 = this.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)); + 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); + + if (entity.getEntityType() != EntityType.STATIC_OBJECT || type == EntityUpdateType.REMOVE) { + snapshots.get(height).add(message); + } + } } \ No newline at end of file From 48b6dc7122b45d50af2e99ed55488d4fc584ea99 Mon Sep 17 00:00:00 2001 From: Steve Date: Fri, 10 Apr 2015 08:01:08 -0400 Subject: [PATCH 2/3] Changed 'region snapshots' from a list to a map, allowing for the removal of previous update operations. (They were stacking before this commit, leading to problems for players just entering the region) It appears that object updating is now functional. :) --- src/org/apollo/game/model/area/Region.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/org/apollo/game/model/area/Region.java b/src/org/apollo/game/model/area/Region.java index 838e560e..6912778a 100644 --- a/src/org/apollo/game/model/area/Region.java +++ b/src/org/apollo/game/model/area/Region.java @@ -75,7 +75,7 @@ public final class Region { /** * 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); + private final List> snapshots = new ArrayList<>(Position.HEIGHT_LEVELS); /** * The Set containing UpdateOperations. @@ -102,7 +102,7 @@ public final class Region { listeners.add(new UpdateRegionListener()); for (int height = 0; height < Position.HEIGHT_LEVELS; height++) { - snapshots.add(new ArrayList<>()); + snapshots.add(new HashMap<>()); updates.add(new ArrayList<>(DEFAULT_SET_SIZE)); } } @@ -220,7 +220,7 @@ public final class Region { * @return The Set of RegionUpdateMessages. */ public List getSnapshot(int height) { - List copy = new ArrayList<>(snapshots.get(height)); + List copy = new ArrayList<>(snapshots.get(height).values()); Collections.sort(copy); return ImmutableList.copyOf(copy); } @@ -313,9 +313,10 @@ public final class Region { int height = entity.getPosition().getHeight(); updates.get(height).add(message); + snapshots.get(height).remove(entity); if (entity.getEntityType() != EntityType.STATIC_OBJECT || type == EntityUpdateType.REMOVE) { - snapshots.get(height).add(message); + snapshots.get(height).put(entity, message); } } From a658e1e8c7306472d0373aeef0048fdbd601cb4a Mon Sep 17 00:00:00 2001 From: Steve Date: Fri, 10 Apr 2015 08:16:58 -0400 Subject: [PATCH 3/3] Slight change in logic, we only need to snapshot static objects during a REMOVE update, and any other entities during an ADD update. --- src/org/apollo/game/model/area/Region.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/org/apollo/game/model/area/Region.java b/src/org/apollo/game/model/area/Region.java index 6912778a..47391e70 100644 --- a/src/org/apollo/game/model/area/Region.java +++ b/src/org/apollo/game/model/area/Region.java @@ -315,7 +315,8 @@ public final class Region { updates.get(height).add(message); snapshots.get(height).remove(entity); - if (entity.getEntityType() != EntityType.STATIC_OBJECT || type == EntityUpdateType.REMOVE) { + if ((entity.getEntityType() == EntityType.STATIC_OBJECT && type == EntityUpdateType.REMOVE) || + (entity.getEntityType() != EntityType.STATIC_OBJECT && type == EntityUpdateType.ADD)) { snapshots.get(height).put(entity, message); } }