diff --git a/src/org/apollo/fs/decoder/GameObjectDecoder.java b/src/org/apollo/fs/decoder/GameObjectDecoder.java index bbd8adb2..addfb9a1 100644 --- a/src/org/apollo/fs/decoder/GameObjectDecoder.java +++ b/src/org/apollo/fs/decoder/GameObjectDecoder.java @@ -11,10 +11,10 @@ import java.util.function.Predicate; import org.apollo.fs.IndexedFileSystem; import org.apollo.fs.decoder.MapFileDecoder.MapDefinition; 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.SectorRepository; import org.apollo.game.model.area.collision.CollisionMatrix; +import org.apollo.game.model.area.obj.ObjectType; import org.apollo.game.model.def.ObjectDefinition; import org.apollo.game.model.entity.GameObject; import org.apollo.util.BufferUtil; @@ -26,23 +26,19 @@ import com.google.common.collect.Iterables; * Parses static object definitions, which include map tiles and landscapes. * * @author Ryley + * @author Major */ public final class GameObjectDecoder { /** - * A bit flag which denotes that a specified Position is blocked. + * A bit flag that indicates that the tile at the current Position is blocked. */ - private static final int FLAG_BLOCKED = 1; + private static final int BLOCKED_TILE = 1; /** - * A bit flag which denotes that a specified Position is a bridge. + * A bit flag that indicates that the tile at the current Position is a bridge tile. */ - private static final int FLAG_BRIDGE = 2; - - /** - * The sector repository. - */ - private static final SectorRepository REPOSITORY = World.getWorld().getSectorRepository(); + private static final int BRIDGE_TILE = 2; /** * The {@link IndexedFileSystem}. @@ -50,97 +46,85 @@ public final class GameObjectDecoder { private final IndexedFileSystem fs; /** - * A {@link List} of decoded game objects. + * A {@link List} of decoded GameObjects. */ private final List objects = new ArrayList<>(); /** - * Creates the decoder. - * - * @param fs The indexed file system. + * The SectorRepository. */ - public GameObjectDecoder(IndexedFileSystem fs) { + private final SectorRepository sectors; + + /** + * Creates the GameObjectDecoder. + * + * @param fs The {@link IndexedFileSystem}. + * @param sectors The {@link SectorRepository}. + */ + public GameObjectDecoder(IndexedFileSystem fs, SectorRepository sectors) { this.fs = fs; + this.sectors = sectors; } /** - * Decodes all static objects and places them in the returned array. + * Decodes the GameObjects from their MapDefinitions. * * @return The decoded objects. - * @throws IOException If an I/O error occurs. + * @throws IOException If there is an error decoding the {@link MapDefinition}s. */ public GameObject[] decode() throws IOException { Map definitions = MapFileDecoder.decode(fs); for (Entry entry : definitions.entrySet()) { - MapDefinition def = entry.getValue(); + MapDefinition definition = entry.getValue(); - int packed = def.getPacketCoordinates(); + int packed = definition.getPackedCoordinates(); int x = (packed >> 8 & 0xFF) * 64; int y = (packed & 0xFF) * 64; - ByteBuffer gameObjectData = fs.getFile(4, def.getObjectFile()); - ByteBuffer gameObjectBuffer = ByteBuffer.wrap(CompressionUtil.degzip(gameObjectData)); - parseGameObject(gameObjectBuffer, x, y); + ByteBuffer objects = fs.getFile(4, definition.getObjectFile()); + ByteBuffer decompressed = ByteBuffer.wrap(CompressionUtil.degzip(objects)); + decodeObjects(decompressed, x, y); - ByteBuffer terrainData = fs.getFile(4, def.getTerrainFile()); - ByteBuffer terrainBuffer = ByteBuffer.wrap(CompressionUtil.degzip(terrainData)); - parseTerrain(terrainBuffer, x, y); + ByteBuffer terrain = fs.getFile(4, definition.getTerrainFile()); + decompressed = ByteBuffer.wrap(CompressionUtil.degzip(terrain)); + decodeTerrain(decompressed, x, y); } return Iterables.toArray(objects, GameObject.class); } - private void parseGameObject(ByteBuffer buffer, int x, int y) { - for (int deltaId, id = -1; (deltaId = BufferUtil.readSmart(buffer)) != 0;) { - id += deltaId; + /** + * Blocks tiles covered by a GameObject, if applicable. + * + * @param object The {@link GameObject}. + * @param position The position of the GameObject. + */ + private void block(GameObject object, Position position) { + ObjectDefinition definition = ObjectDefinition.lookup(object.getId()); + int type = object.getType(); - for (int deltaPos, pos = 0; (deltaPos = BufferUtil.readSmart(buffer)) != 0;) { - pos += deltaPos - 1; - - int localY = pos & 0x3F; - int localX = pos >> 6 & 0x3F; - int height = pos >> 12; - - int attributes = buffer.get() & 0xFF; - int type = attributes >> 2; - int orientation = attributes & 0x3; - Position position = new Position(x + localX, y + localY, height); - - gameObjectDecoded(id, orientation, type, position); - } - } - } - - private void gameObjectDecoded(int id, int orientation, int type, Position position) { - ObjectDefinition definition = ObjectDefinition.lookup(id); - - Sector sector = REPOSITORY.fromPosition(position); + Sector sector = sectors.fromPosition(position); int x = position.getX(), y = position.getY(), height = position.getHeight(); - // FIXME: For some reason the height is negative on some occasions - if (height < 0) { - return; - } - CollisionMatrix matrix = sector.getMatrix(height); boolean block = false; - // Ground decoration, signs, water fountains, etc - if (type == 22 && definition.isInteractive()) { + if (type == ObjectType.FLOOR_DECORATION.getValue() && definition.isInteractive()) { block = true; } - Predicate walls = (value) -> value >= 0 && value < 4 || value == 9; - Predicate roofs = (value) -> value >= 12 && value < 22; + Predicate walls = (value) -> value >= ObjectType.LENGTHWISE_WALL.getValue() + && value <= ObjectType.RECTANGULAR_CORNER.getValue() || value == ObjectType.DIAGONAL_WALL.getValue(); + + Predicate roofs = (value) -> value > ObjectType.DIAGONAL_INTERACTABLE.getValue() + && value < ObjectType.FLOOR_DECORATION.getValue(); - // Walls and roofs that intercept may intercept a mob when moving if (walls.test(type) || roofs.test(type)) { block = true; } - // General objects, trees, statues, etc if (type == 10 && definition.isSolid()) { block = true; } @@ -154,11 +138,14 @@ public final class GameObjectDecoder { int nextLocalX = localX > 7 ? x + localX - 7 : x + localX; int nextLocalY = localY > 7 ? y + localY - 7 : y - localY; Position nextPosition = new Position(nextLocalX, nextLocalY); - Sector next = REPOSITORY.fromPosition(nextPosition); + Sector next = sectors.fromPosition(nextPosition); - int nextX = (nextPosition.getX() % Sector.SECTOR_SIZE) + dx, nextY = (nextPosition.getY() % Sector.SECTOR_SIZE) + dy; - if(nextX > 7) nextX -= 7; - if(nextY > 7) nextY -= 7; + int nextX = (nextPosition.getX() % Sector.SECTOR_SIZE) + dx, nextY = (nextPosition.getY() % Sector.SECTOR_SIZE) + + dy; + if (nextX > 7) + nextX -= 7; + if (nextY > 7) + nextY -= 7; next.getMatrix(height).block(nextX, nextY); continue; @@ -168,56 +155,28 @@ public final class GameObjectDecoder { } } } - - objects.add(new GameObject(id, position, type, orientation)); } - private void parseTerrain(ByteBuffer buffer, int x, int y) { - for (int height = 0; height < 4; height++) { - for (int localX = 0; localX < 64; localX++) { - for (int localY = 0; localY < 64; localY++) { - Position position = new Position(x + localX, y + localY, height); - - int flags = 0; - while (true) { - int attributeId = buffer.get() & 0xFF; - if (attributeId == 0) { - terrainDecoded(flags, position); - break; - } else if (attributeId == 1) { - buffer.get(); - terrainDecoded(flags, position); - break; - } else if (attributeId <= 49) { - buffer.get(); - } else if (attributeId <= 81) { - flags = attributeId - 49; - } - } - } - } - } - } - - private void terrainDecoded(int flags, Position position) { - Sector sector = REPOSITORY.fromPosition(position); + /** + * Decodes the attributes of a terrain file, blocking the tile if necessary. + * + * @param attributes The terrain attributes. + * @param position The {@link Position} of the tile whose attributes are being decoded. + */ + private void decodeAttributes(int attributes, Position position) { + Sector sector = sectors.fromPosition(position); int x = position.getX(), y = position.getY(), height = position.getHeight(); - // FIXME: For some reason the height is negative on some occasions - if (height < 0) { - return; - } - CollisionMatrix current = sector.getMatrix(height); boolean block = false; - if ((flags & FLAG_BLOCKED) != 0) { + if ((attributes & BLOCKED_TILE) != 0) { block = true; } - - if ((flags & FLAG_BRIDGE) != 0) { - if (--height >= 0) { + if ((attributes & BRIDGE_TILE) != 0) { + if (height > 0) { block = true; + height--; } } @@ -227,4 +186,78 @@ public final class GameObjectDecoder { } } + /** + * Decodes object data stored in the specified {@link ByteBuffer}. + * + * @param buffer The ByteBuffer. + * @param x The x coordinate of the top left tile of the map file. + * @param y The y coordinate of the top left tile of the map file. + */ + private void decodeObjects(ByteBuffer buffer, int x, int y) { + int id = -1; + int idOffset = BufferUtil.readSmart(buffer); + + while (idOffset != 0) { + id += idOffset; + + int packed = 0; + int positionOffset = BufferUtil.readSmart(buffer); + + while (positionOffset != 0) { + packed += positionOffset - 1; + + int localY = packed & 0x3F; + int localX = packed >> 6 & 0x3F; + int height = (packed >> 12) & 0x3; + + int attributes = buffer.get() & 0xFF; + int type = attributes >> 2; + int orientation = attributes & 0x3; + Position position = new Position(x + localX, y + localY, height); + + GameObject object = new GameObject(id, position, type, orientation); + objects.add(object); + + block(object, position); + positionOffset = BufferUtil.readSmart(buffer); + } + + idOffset = BufferUtil.readSmart(buffer); + } + } + + /** + * Decodes terrain data stored in the specified {@link ByteBuffer}. + * + * @param buffer The ByteBuffer. + * @param x The x coordinate of the top left tile of the map file. + * @param y The y coordinate of the top left tile of the map file. + */ + private void decodeTerrain(ByteBuffer buffer, int x, int y) { + for (int height = 0; height < 4; height++) { + for (int localX = 0; localX < 64; localX++) { + for (int localY = 0; localY < 64; localY++) { + Position position = new Position(x + localX, y + localY, height); + + int attributes = 0; + while (true) { + int attributeId = buffer.get() & 0xFF; + if (attributeId == 0) { + decodeAttributes(attributes, position); + break; + } else if (attributeId == 1) { + buffer.get(); + decodeAttributes(attributes, position); + break; + } else if (attributeId <= 49) { + buffer.get(); + } else if (attributeId <= 81) { + attributes = attributeId - 49; + } + } + } + } + } + } + } \ No newline at end of file diff --git a/src/org/apollo/fs/decoder/MapFileDecoder.java b/src/org/apollo/fs/decoder/MapFileDecoder.java index ee78724e..a5eb534c 100644 --- a/src/org/apollo/fs/decoder/MapFileDecoder.java +++ b/src/org/apollo/fs/decoder/MapFileDecoder.java @@ -8,109 +8,127 @@ import java.util.Map; import org.apollo.fs.IndexedFileSystem; import org.apollo.fs.archive.Archive; import org.apollo.fs.archive.ArchiveEntry; +import org.apollo.game.model.area.Sector; /** - * Decodes {@link MapDefinition map definitions} from the {@link IndexedFileSystem}. + * Decodes {@link MapDefinition}s from the {@link IndexedFileSystem}. * * @author Ryley + * @author Major */ public final class MapFileDecoder { + /** + * The width (and length) of a map file, in tiles. + */ + public static final int MAP_FILE_WIDTH = Sector.SECTOR_SIZE * Sector.SECTOR_SIZE; + + /** + * The file id of the versions archive. + */ + private static final int VERSIONS_ARCHIVE_FILE_ID = 5; + /** * Decodes {@link MapDefinition}s from the specified {@link IndexedFileSystem}. * - * @param fs The file system. - * @return A {@link Map} of parsed map definitions. - * @throws IOException If some I/O error occurs. + * @param fs The IndexedFileSystem. + * @return A {@link Map} of packed coordinates to their MapDefinitions. + * @throws IOException If there is an error reading or decoding the Archive. */ protected static Map decode(IndexedFileSystem fs) throws IOException { - Archive archive = Archive.decode(fs.getFile(0, 5)); + Archive archive = Archive.decode(fs.getFile(0, VERSIONS_ARCHIVE_FILE_ID)); ArchiveEntry entry = archive.getEntry("map_index"); + Map definitions = new HashMap<>(); + ByteBuffer buffer = entry.getBuffer(); - Map defs = new HashMap<>(); + int count = buffer.capacity() / (3 * Short.BYTES + Byte.BYTES); - int count = buffer.capacity() / 7; - for (int i = 0; i < count; i++) { - int packedCoordinates = buffer.getShort() & 0xFFFF; - int terrainFile = buffer.getShort() & 0xFFFF; - int objectFile = buffer.getShort() & 0xFFFF; - boolean preload = buffer.get() == 1; + for (int times = 0; times < count; times++) { + int packed = buffer.getShort() & 0xFFFF; + int terrain = buffer.getShort() & 0xFFFF; + int objects = buffer.getShort() & 0xFFFF; + boolean members = buffer.get() == 1; - defs.put(packedCoordinates, new MapDefinition(packedCoordinates, terrainFile, objectFile, preload)); + definitions.put(packed, new MapDefinition(packed, terrain, objects, members)); } - return defs; + return definitions; } /** - * Represents a single map definition. - * - * @author Ryley + * A definition for a region. */ public static final class MapDefinition { /** * The packed coordinates. */ - private final int packetCoordinates; + private final int packedCoordinates; /** * The terrain file id. */ - private final int terrainFile; + private final int terrain; /** * The object file id. */ - private final int objectFile; + private final int objects; /** - * Whether or not this map is preloaded. + * Indicates whether or not this map is members-only. */ - private final boolean preload; + private final boolean members; /** - * Constructs a new {@link MapDefinition} with the specified packed coordinates, terrain file id, object file id - * and preload state. - * + * Creates the {@link MapDefinition}. + * * @param packedCoordinates The packed coordinates. - * @param terrainFile The terrain file id. - * @param objectFile The object file id. - * @param preload Whether or not this map is preloaded. + * @param terrain The terrain file id. + * @param objects The object file id. + * @param members Indicates whether or not this map is members-only. */ - public MapDefinition(int packedCoordinates, int terrainFile, int objectFile, boolean preload) { - this.packetCoordinates = packedCoordinates; - this.terrainFile = terrainFile; - this.objectFile = objectFile; - this.preload = preload; + public MapDefinition(int packedCoordinates, int terrain, int objects, boolean members) { + this.packedCoordinates = packedCoordinates; + this.terrain = terrain; + this.objects = objects; + this.members = members; } /** - * Returns the packed coordinates. + * Gets the packed coordinates. + * + * @return The packed coordinates. */ - public int getPacketCoordinates() { - return packetCoordinates; + public int getPackedCoordinates() { + return packedCoordinates; } /** - * Returns the terrain file id. + * Gets the id of the file containing the terrain data. + * + * @return The file id. */ public int getTerrainFile() { - return terrainFile; + return terrain; } /** - * Returns the object file id. + * Gets the id of the file containing the object data. + * + * @return The file id. */ public int getObjectFile() { - return objectFile; + return objects; } /** - * Returns whether or not this map is preloaded. + * Returns whether or not this MapDefinition is for a members-only area of the world. + * + * @return {@code true} if this MapDefinition is for a members-only area, {@code false} if not. */ - public boolean isPreload() { - return preload; + public boolean isMembersOnly() { + return members; } } diff --git a/src/org/apollo/game/model/World.java b/src/org/apollo/game/model/World.java index 98335a0b..b0df8ec1 100644 --- a/src/org/apollo/game/model/World.java +++ b/src/org/apollo/game/model/World.java @@ -243,7 +243,7 @@ public final class World { ObjectDefinition.init(objectDefs); logger.fine("Loaded " + objectDefs.length + " object definitions."); - GameObjectDecoder staticDecoder = new GameObjectDecoder(fs); + GameObjectDecoder staticDecoder = new GameObjectDecoder(fs, sectors); GameObject[] objects = staticDecoder.decode(); placeEntities(objects); logger.fine("Loaded " + objects.length + " static objects."); diff --git a/src/org/apollo/game/model/area/obj/ObjectGroup.java b/src/org/apollo/game/model/area/obj/ObjectGroup.java new file mode 100644 index 00000000..4faa4211 --- /dev/null +++ b/src/org/apollo/game/model/area/obj/ObjectGroup.java @@ -0,0 +1,68 @@ +package org.apollo.game.model.area.obj; + +import java.util.Arrays; + +/** + * The group of an object, which indicates its general class (e.g. if it's a wall, or a floor decoration). + * + * @author Major + * @author Scu11 + */ +public enum ObjectGroup { + + /** + * The wall object group, which may block a tile. + */ + WALL(0), + + /** + * The wall decoration object group, which never blocks a tile. + */ + WALL_DECORATION(1), + + /** + * The interactable object group, for objects that can be clicked and interacted with. + */ + INTERACTABLE_OBJECT(2), + + /** + * The ground decoration object group, which may block a tile. + */ + GROUND_DECORATION(3); + + /** + * Gets the ObjectGroup with the specified integer value. + * + * @param value The integer value of the ObjectGroup. + * @return The ObjectGroup. + * @throws IllegalArgumentException If there is no ObjectGroup with the specified value. + */ + public static ObjectGroup valueOf(int value) { + return Arrays.stream(values()).filter(group -> group.value == value).findAny() + .orElseThrow(() -> new IllegalArgumentException("No ObjectGroup with a value of " + value + " exists.")); + } + + /** + * The integer value of the group. + */ + private final int value; + + /** + * Creates the ObjectGroup. + * + * @param value The integer value of the group. + */ + private ObjectGroup(int value) { + this.value = value; + } + + /** + * Gets the value of this ObjectGroup. + * + * @return The value. + */ + public int getValue() { + return value; + } + +} \ No newline at end of file diff --git a/src/org/apollo/game/model/area/obj/ObjectType.java b/src/org/apollo/game/model/area/obj/ObjectType.java new file mode 100644 index 00000000..c73a989e --- /dev/null +++ b/src/org/apollo/game/model/area/obj/ObjectType.java @@ -0,0 +1,91 @@ +package org.apollo.game.model.area.obj; + +/** + * The type of an object, which affects specified behaviour (such as whether it displaces existing objects). TODO + * complete this... + * + * @author Major + * @author Scu11 + */ +public enum ObjectType { + + /** + * A wall that is presented lengthwise with respect to the tile. + */ + LENGTHWISE_WALL(0, ObjectGroup.WALL), + + /** + * A triangular object positioned in the corner of the tile. + */ + TRIANGULAR_CORNER(1, ObjectGroup.WALL), + + /** + * A corner for a wall, where the model is placed on two perpendicular edges of a single tile. + */ + WALL_CORNER(2, ObjectGroup.WALL), + + /** + * A rectangular object positioned in the corner of the tile. + */ + RECTANGULAR_CORNER(3, ObjectGroup.WALL), + + /** + * A wall joint that is presented diagonally with respect to the tile. + */ + DIAGONAL_WALL(9, ObjectGroup.INTERACTABLE_OBJECT), + + /** + * An object that can be interacted with by a player. + */ + INTERACTABLE(10, ObjectGroup.INTERACTABLE_OBJECT), + + /** + * An {@link #INTERACTABLE} object, rotated {@code pi / 2} radians. + */ + DIAGONAL_INTERACTABLE(11, ObjectGroup.INTERACTABLE_OBJECT), + + /** + * A decoration positioned on the floor. + */ + FLOOR_DECORATION(22, ObjectGroup.GROUND_DECORATION); + + /** + * The ObjectGroup this type belongs in. + */ + private final ObjectGroup group; + + /** + * The integer value of this ObjectType. + */ + private final int value; + + /** + * Creates the ObjectType. + * + * @param value The integer value of this ObjectType. + * @param group The ObjectGroup of this type. + */ + private ObjectType(int value, ObjectGroup group) { + this.value = value; + this.group = group; + } + + /** + * Gets the {@link ObjectGroup} of this ObjectType. + * + * @return The group. + */ + public ObjectGroup getGroup() { + return group; + } + + /** + * Gets the integer value of this ObjectType. + * + * @return The value. + */ + public int getValue() { + return value; + } + +} \ No newline at end of file diff --git a/src/org/apollo/game/model/area/obj/package-info.java b/src/org/apollo/game/model/area/obj/package-info.java new file mode 100644 index 00000000..df49f8d5 --- /dev/null +++ b/src/org/apollo/game/model/area/obj/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains object-related classes. + */ +package org.apollo.game.model.area.obj; \ No newline at end of file