diff --git a/src/org/apollo/game/model/area/Sector.java b/src/org/apollo/game/model/area/Sector.java index e1c35d46..3f582fe1 100644 --- a/src/org/apollo/game/model/area/Sector.java +++ b/src/org/apollo/game/model/area/Sector.java @@ -8,7 +8,9 @@ import java.util.Map; import java.util.Set; import java.util.stream.Collectors; +import org.apollo.game.model.Direction; import org.apollo.game.model.Position; +import org.apollo.game.model.area.collision.CollisionMatrix; import org.apollo.game.model.entity.Entity; import org.apollo.game.model.entity.Entity.EntityType; @@ -23,7 +25,7 @@ import com.google.common.collect.ImmutableSet; public final class Sector { /** - * The width and length of a sector, in tiles. + * The width and length of a Sector, in tiles. */ public static final int SECTOR_SIZE = 8; @@ -33,20 +35,25 @@ public final class Sector { private static final int DEFAULT_SET_SIZE = 2; /** - * The sector coordinates of this sector. + * The SectorCoordinates of this Sector. */ private final SectorCoordinates coordinates; /** - * A map of positions to entities in that position. + * The Map of Positions to Entities in that Position. */ private final Map> entities = new HashMap<>(); /** - * A list of listeners registered to this sector. + * A List of SectorListeners registered to this Sector. */ private final List listeners = new ArrayList<>(); + /** + * The CollisionMatrix. + */ + private final CollisionMatrix matrix = new CollisionMatrix(SECTOR_SIZE, SECTOR_SIZE); + /** * Creates a new sector. * @@ -67,28 +74,29 @@ public final class Sector { } /** - * Adds a {@link Entity} from to sector. Note that this does not spawn the entity, or do any other action other than + * Adds a {@link Entity} from to sector. Note that this does not spawn the Entity, or do any other action other than * register it to this sector. * - * @param entity The entity. - * @throws IllegalArgumentException If the entity does not belong in this sector. + * @param entity The Entity. + * @throws IllegalArgumentException If the Entity does not belong in this sector. */ public void addEntity(Entity entity) { Position position = entity.getPosition(); checkPosition(position); - Set local = entities.computeIfAbsent(position, key -> new HashSet<>(DEFAULT_SET_SIZE)); + Set local = entities.computeIfAbsent(position, key -> new HashSet<>(DEFAULT_SET_SIZE)); local.add(entity); + notifyListeners(entity, SectorOperation.ADD); } /** - * Checks if this sector contains the specified entity. + * 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}. + * @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(); @@ -114,12 +122,13 @@ public final class Sector { * @return The list. */ public Set getEntities(Position position) { - return ImmutableSet.copyOf(entities.computeIfAbsent(position, key -> new HashSet<>(DEFAULT_SET_SIZE))); + Set set = entities.get(position); + return (set == null) ? ImmutableSet.of() : ImmutableSet.copyOf(set); } /** * Gets a shallow copy of the {@link Set} of {@link Entity}s with the specified {@link EntityType}. The returned - * type will be immutable. Type will be inferred from the call, so ensure that the entity type and the reference + * 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. @@ -127,21 +136,24 @@ public final class Sector { * @return The set of entities. */ public Set getEntities(Position position, EntityType type) { - Set local = entities.computeIfAbsent(position, key -> new HashSet<>(DEFAULT_SET_SIZE)); + Set local = entities.get(position); + if (local == null) { + return ImmutableSet.of(); + } @SuppressWarnings("unchecked") - Set filtered = (Set) local.stream().filter(entity -> entity.getEntityType() == type).collect(Collectors.toSet()); + Set filtered = (Set) local.stream().filter(Entity -> Entity.getEntityType() == type).collect(Collectors.toSet()); return ImmutableSet.copyOf(filtered); } /** * Moves the {@link Entity} that was in the specified {@code old} {@link Position}, to the current position of the - * entity. + * Entity. *

- * Both the {@code old} and current positions of the entity must belong to this sector. + * Both the {@code old} and current positions of the Entity must belong to this sector. * - * @param old The old position of the entity. - * @param entity The entity to move. + * @param old The old position of the Entity. + * @param entity The Entity to move. * @throws IllegalArgumentException If either of the positions do not belong to this sector. */ public void moveEntity(Position old, Entity entity) { @@ -175,8 +187,8 @@ public final class Sector { /** * Removes a {@link Entity} from this sector. * - * @param entity The entity. - * @throws IllegalArgumentException If the entity does not belong in this sector, or if it was never added. + * @param entity The Entity. + * @throws IllegalArgumentException If the Entity does not belong in this sector, or if it was never added. */ public void removeEntity(Entity entity) { Position position = entity.getPosition(); @@ -191,6 +203,20 @@ public final class Sector { notifyListeners(entity, SectorOperation.REMOVE); } + /** + * Returns whether or not an Entity of the specified {@link EntityType type} can traverse the tile at the specified + * coordinate pair. + * + * @param x The x coordinate. + * @param y The y coordinate. + * @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(int x, int y, EntityType entity, Direction direction) { + return matrix.traversable(x, y, entity, direction); + } + /** * Checks that the specified {@link Position} is included in this sector. * diff --git a/src/org/apollo/game/model/area/collision/CollisionFlag.java b/src/org/apollo/game/model/area/collision/CollisionFlag.java new file mode 100644 index 00000000..b129511b --- /dev/null +++ b/src/org/apollo/game/model/area/collision/CollisionFlag.java @@ -0,0 +1,100 @@ +package org.apollo.game.model.area.collision; + +/** + * A type of flag in a {@link CollisionMatrix}. + * + * @author Major + */ +public enum CollisionFlag { + + /** + * The walk north flag. + */ + MOB_NORTH(0), + + /** + * The walk east flag. + */ + MOB_EAST(1), + + /** + * The walk south flag. + */ + MOB_SOUTH(2), + + /** + * The walk west flag. + */ + MOB_WEST(3), + + /** + * The projectile north flag. + */ + PROJECTILE_NORTH(4), + + /** + * The projectile east flag. + */ + PROJECTILE_EAST(5), + + /** + * The projectile south flag. + */ + PROJECTILE_SOUTH(6), + + /** + * The projectile west flag. + */ + PROJECTILE_WEST(7); + + /** + * Returns an array of CollisionFlags that indicate if a Mob can traverse over a tile. + * + * @return The array of CollisionFlags. + */ + public static CollisionFlag[] mobs() { + return new CollisionFlag[] { MOB_NORTH, MOB_EAST, MOB_SOUTH, MOB_WEST }; + } + + /** + * Returns an array of CollisionFlags that indicate if a Projectile can traverse over a tile. + * + * @return The array of CollisionFlags. + */ + public static CollisionFlag[] projectiles() { + return new CollisionFlag[] { PROJECTILE_NORTH, PROJECTILE_EAST, PROJECTILE_SOUTH, PROJECTILE_WEST }; + } + + /** + * The index of the bit this flag is stored in. + */ + private final int bit; + + /** + * Creates the CollisionFlag. + * + * @param bit The index of the bit this flag is stored in. + */ + private CollisionFlag(int bit) { + this.bit = bit; + } + + /** + * Gets this CollisionFlag, as a {@code byte}. + * + * @return The value, as a {@code byte}. + */ + public byte asByte() { + return (byte) (1 << bit); + } + + /** + * Gets the index of the bit this flag is stored in. + * + * @return The index of the bit. + */ + public int getBit() { + return bit; + } + +} \ No newline at end of file diff --git a/src/org/apollo/game/model/area/collision/CollisionMatrix.java b/src/org/apollo/game/model/area/collision/CollisionMatrix.java new file mode 100644 index 00000000..6c370591 --- /dev/null +++ b/src/org/apollo/game/model/area/collision/CollisionMatrix.java @@ -0,0 +1,227 @@ +package org.apollo.game.model.area.collision; + +import java.util.Arrays; + +import org.apollo.game.model.Direction; +import org.apollo.game.model.entity.Entity.EntityType; + +import com.google.common.base.MoreObjects; +import com.google.common.base.Preconditions; + +/** + * A 2-dimensional adjacency matrix containing tile collision data. + * + * @author Major + */ +public final class CollisionMatrix { + + /** + * Indicates that all types of traversal are allowed. + */ + private static final byte ALL_ALLOWED = 0b0000_0000; + + /** + * Indicates that no types of traversal are allowed. + */ + private static final byte ALL_BLOCKED = (byte) 0b1111_1111; + + /** + * The length of the matrix. + */ + private final int length; + + /** + * The collision matrix, as a {@code byte} array. + */ + private final byte[] matrix; + + /** + * The width of the matrix. + */ + private final int width; + + /** + * Creates the CollisionMatrix. + * + * @param width The width of the matrix. + * @param length The length of the matrix. + */ + public CollisionMatrix(int width, int length) { + this.width = width; + this.length = length; + matrix = new byte[width * length]; + } + + /** + * Returns whether or not all of the specified {@link CollisionFlag}s are set for the specified + * coordinate pair. + * + * @param x The x coordinate. + * @param y The y coordinate. + * @param flags The CollisionFlags. + * @return {@code true} if all of the CollisionFlags are set, otherwise {@code false}. + */ + public boolean all(int x, int y, CollisionFlag... flags) { + for (CollisionFlag flag : flags) { + if ((get(x, y) & flag.asByte()) == 0) { + return false; + } + } + + return true; + } + + /** + * Returns whether or not any of the specified {@link CollisionFlag}s are set for the specified + * coordinate pair. + * + * @param x The x coordinate. + * @param y The y coordinate. + * @param flags The CollisionFlags. + * @return {@code true} if any of the CollisionFlags are set, otherwise {@code false}. + */ + public boolean any(int x, int y, CollisionFlag... flags) { + for (CollisionFlag flag : flags) { + if ((get(x, y) & flag.asByte()) != 0) { + return true; + } + } + + return false; + } + + /** + * Completely blocks the tile at the specified coordinate pair. + * + * @param x The x coordinate. + * @param y The y coordinate. + */ + public void block(int x, int y) { + set(x, y, ALL_BLOCKED); + } + + /** + * Clears (i.e. sets to {@code false}) the value of the specified {@link CollisionFlag} for the specified coordinate + * pair. + * + * @param x The x coordinate. + * @param y The y coordinate. + * @param flag The CollisionFlag. + */ + public void clear(int x, int y, CollisionFlag flag) { + set(x, y, (byte) ~flag.asByte()); + } + + /** + * Returns whether or not the specified {@link CollisionFlag} is set for the specified coordinate pair. + * + * @param x The x coordinate. + * @param y The y coordinate. + * @param flag The CollisionFlag. + * @return {@code true} if the CollisionFlag is set, {@code false} if not. + */ + public boolean flagged(int x, int y, CollisionFlag flag) { + return (get(x, y) & flag.asByte()) != 0; + } + + /** + * Gets the value of the specified tile. + * + * @param x The x coordinate of the tile. + * @param y The y coordinate of the tile. + * @return The value. + */ + public int get(int x, int y) { + return matrix[indexOf(x, y)] & 0xFF; + } + + /** + * Resets the cell of the specified coordinate pair. + * + * @param x The x coordinate. + * @param y The y coordinate. + */ + public void reset(int x, int y) { + set(x, y, ALL_ALLOWED); + } + + /** + * Sets (i.e. sets to {@code true}) the value of the specified {@link CollisionFlag} for the specified coordinate + * pair. + * + * @param x The x coordinate. + * @param y The y coordinate. + * @param flag The CollisionFlag. + */ + public void set(int x, int y, CollisionFlag flag) { + set(x, y, flag.asByte()); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this).add("width", width).add("length", length).add("matrix", Arrays.toString(matrix)) + .toString(); + } + + /** + * Returns whether or not an Entity of the specified {@link EntityType type} can traverse the tile at the specified + * coordinate pair. + * + * @param x The x coordinate. + * @param y The y coordinate. + * @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(int x, int y, EntityType entity, Direction direction) { + CollisionFlag[] flags = (entity == EntityType.PROJECTILE) ? CollisionFlag.projectiles() : CollisionFlag.mobs(); + int north = 0, east = 1, south = 2, west = 3; + + switch (direction) { + case NORTH_WEST: + return any(x, y, flags[south], flags[east]); + case NORTH: + return flagged(x, y, flags[south]); + case NORTH_EAST: + return any(x, y, flags[south], flags[west]); + case EAST: + return flagged(x, y, flags[west]); + case SOUTH_EAST: + return any(x, y, flags[north], flags[west]); + case SOUTH: + return flagged(x, y, flags[north]); + case SOUTH_WEST: + return any(x, y, flags[north], flags[east]); + case WEST: + return flagged(x, y, flags[east]); + } + + throw new IllegalArgumentException("Unrecognised direction " + direction + "."); + } + + /** + * Gets the index in the matrix for the specified coordinate pair. + * + * @param x The x coordinate. + * @param y The y coordinate. + * @return The index. + * @throws ArrayIndexOutOfBoundsException If the specified coordinate pair does not fit in this matrix. + */ + private int indexOf(int x, int y) { + int index = y * width + x; + Preconditions.checkElementIndex(index, matrix.length, "Index out of bounds."); + return index; + } + + /** + * Sets the appropriate index for the specified coordinate pair to the specified value. + * + * @param x The x coordinate. + * @param y The y coordinate. + * @param value The value. + */ + private void set(int x, int y, byte value) { + matrix[indexOf(x, y)] = value; + } + +} \ No newline at end of file diff --git a/src/org/apollo/game/model/area/collision/package-info.java b/src/org/apollo/game/model/area/collision/package-info.java new file mode 100644 index 00000000..aeb5e04c --- /dev/null +++ b/src/org/apollo/game/model/area/collision/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains classes related to tile collision data. + */ +package org.apollo.game.model.area.collision; \ No newline at end of file