From c8b73c871ea2bf4a42f9069f10eb0737d73809b4 Mon Sep 17 00:00:00 2001 From: Major- Date: Sun, 3 Nov 2013 04:02:06 +0000 Subject: [PATCH] Add npc and object definition decoding. --- .../ItemDefinitionDecoder.java} | 54 +-- .../fs/decoder/NpcDefinitionDecoder.java | 171 +++++++++ .../fs/decoder/ObjectDefinitionDecoder.java | 161 ++++++++ .../fs/decoder/StaticObjectDecoder.java | 119 ++++++ .../fs/{parser => decoder}/package-info.java | 2 +- .../event/handler/impl/EquipEventHandler.java | 2 - src/org/apollo/game/model/Npc.java | 52 +++ src/org/apollo/game/model/World.java | 54 ++- .../apollo/game/model/def/ItemDefinition.java | 25 +- .../apollo/game/model/def/NpcDefinition.java | 362 ++++++++++++++++++ .../game/model/def/ObjectDefinition.java | 272 +++++++++++++ src/org/apollo/game/model/obj/GameObject.java | 35 ++ .../apollo/game/model/obj/StaticObject.java | 107 +++++- src/org/apollo/tools/EquipmentUpdater.java | 6 +- src/org/apollo/tools/NoteUpdater.java | 6 +- src/org/apollo/util/ByteBufferUtil.java | 22 +- src/org/apollo/util/CompressionUtil.java | 35 ++ 17 files changed, 1409 insertions(+), 76 deletions(-) rename src/org/apollo/fs/{parser/ItemDefinitionParser.java => decoder/ItemDefinitionDecoder.java} (77%) create mode 100644 src/org/apollo/fs/decoder/NpcDefinitionDecoder.java create mode 100644 src/org/apollo/fs/decoder/ObjectDefinitionDecoder.java create mode 100644 src/org/apollo/fs/decoder/StaticObjectDecoder.java rename src/org/apollo/fs/{parser => decoder}/package-info.java (69%) create mode 100644 src/org/apollo/game/model/Npc.java create mode 100644 src/org/apollo/game/model/def/NpcDefinition.java create mode 100644 src/org/apollo/game/model/def/ObjectDefinition.java create mode 100644 src/org/apollo/game/model/obj/GameObject.java diff --git a/src/org/apollo/fs/parser/ItemDefinitionParser.java b/src/org/apollo/fs/decoder/ItemDefinitionDecoder.java similarity index 77% rename from src/org/apollo/fs/parser/ItemDefinitionParser.java rename to src/org/apollo/fs/decoder/ItemDefinitionDecoder.java index edd28945..f3f268e2 100644 --- a/src/org/apollo/fs/parser/ItemDefinitionParser.java +++ b/src/org/apollo/fs/decoder/ItemDefinitionDecoder.java @@ -1,4 +1,4 @@ -package org.apollo.fs.parser; +package org.apollo.fs.decoder; import java.io.IOException; import java.nio.ByteBuffer; @@ -9,35 +9,35 @@ import org.apollo.game.model.def.ItemDefinition; import org.apollo.util.ByteBufferUtil; /** - * A class which parses item definitions. + * Decodes item data from the {@code obj.dat} file into {@link ItemDefinition}s. * * @author Graham */ -public final class ItemDefinitionParser { +public final class ItemDefinitionDecoder { /** - * The indexed file system. + * The {@link IndexedFileSystem}. */ private final IndexedFileSystem fs; /** - * Creates the item definition parser. + * Creates the item definition decoder. * * @param fs The indexed file system. */ - public ItemDefinitionParser(IndexedFileSystem fs) { + public ItemDefinitionDecoder(IndexedFileSystem fs) { this.fs = fs; } /** - * Parses the item definitions. + * Decodes the item definitions. * * @return The item definitions. * @throws IOException If an I/O error occurs. */ - public ItemDefinition[] parse() throws IOException { + public ItemDefinition[] decode() throws IOException { Archive config = Archive.decode(fs.getFile(0, 2)); - ByteBuffer dat = config.getEntry("obj.dat").getBuffer(); + ByteBuffer data = config.getEntry("obj.dat").getBuffer(); ByteBuffer idx = config.getEntry("obj.idx").getBuffer(); int count = idx.getShort(); @@ -50,33 +50,33 @@ public final class ItemDefinitionParser { ItemDefinition[] defs = new ItemDefinition[count]; for (int i = 0; i < count; i++) { - dat.position(indices[i]); - defs[i] = parseDefinition(i, dat); + data.position(indices[i]); + defs[i] = decode(i, data); } return defs; } /** - * Parses a single definition. + * Decodes a single definition. * * @param id The item's id. * @param buffer The buffer. - * @return The definition. + * @return The {@link ItemDefinition}. */ - private ItemDefinition parseDefinition(int id, ByteBuffer buffer) { - ItemDefinition def = new ItemDefinition(id); + private ItemDefinition decode(int id, ByteBuffer buffer) { + ItemDefinition definition = new ItemDefinition(id); while (true) { int code = buffer.get() & 0xFF; if (code == 0) { - return def; + return definition; } else if (code == 1) { buffer.getShort();// & 0xFFFF; // model Id } else if (code == 2) { - def.setName(ByteBufferUtil.readString(buffer)); + definition.setName(ByteBufferUtil.readString(buffer)); } else if (code == 3) { - def.setDescription(ByteBufferUtil.readString(buffer)); + definition.setDescription(ByteBufferUtil.readString(buffer)); } else if (code == 4) { buffer.getShort();// & 0xFFFF; // sprite scale } else if (code == 5) { @@ -88,13 +88,13 @@ public final class ItemDefinitionParser { } else if (code == 8) { buffer.getShort(); // sprite dY } else if (code == 10) { - buffer.getShort();// & 0xFFFF; + buffer.getShort(); } else if (code == 11) { - def.setStackable(true); + definition.setStackable(true); } else if (code == 12) { buffer.getInt(); // model height } else if (code == 16) { - def.setMembersOnly(true); + definition.setMembersOnly(true); } else if (code == 23) { buffer.getShort(); // & 0xFFFF; //primaryMaleEquipModelId buffer.get(); // maleEquipYTranslation @@ -110,10 +110,10 @@ public final class ItemDefinitionParser { if (str.equalsIgnoreCase("hidden")) { str = null; } - def.setGroundAction(code - 30, str); + definition.setGroundAction(code - 30, str); } else if (code >= 35 && code < 40) { String str = ByteBufferUtil.readString(buffer); - def.setInventoryAction(code - 35, str); + definition.setInventoryAction(code - 35, str); } else if (code == 40) { int colourCount = buffer.get() & 0xFF; for (int i = 0; i < colourCount; i++) { @@ -136,10 +136,10 @@ public final class ItemDefinitionParser { buffer.getShort(); // & 0xFFFF; // spriteCameraYaw } else if (code == 97) { int noteInfoId = buffer.getShort() & 0xFFFF; - def.setNoteInfoId(noteInfoId); + definition.setNoteInfoId(noteInfoId); } else if (code == 98) { int noteGraphicId = buffer.getShort() & 0xFFFF; - def.setNoteGraphicId(noteGraphicId); + definition.setNoteGraphicId(noteGraphicId); } else if (code >= 100 && code < 110) { buffer.getShort(); // & 0xFFFF; // stack id buffer.getShort(); // & 0xFFFF; // stack size @@ -154,9 +154,9 @@ public final class ItemDefinitionParser { } else if (code == 114) { buffer.getShort(); // * 5; // light diffusion } else if (code == 115) { - buffer.get(); // & 0xFF; // team + definition.setTeam(buffer.get() & 0xFF); } } } -} +} \ No newline at end of file diff --git a/src/org/apollo/fs/decoder/NpcDefinitionDecoder.java b/src/org/apollo/fs/decoder/NpcDefinitionDecoder.java new file mode 100644 index 00000000..1ad4a0eb --- /dev/null +++ b/src/org/apollo/fs/decoder/NpcDefinitionDecoder.java @@ -0,0 +1,171 @@ +package org.apollo.fs.decoder; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import org.apollo.fs.IndexedFileSystem; +import org.apollo.fs.archive.Archive; +import org.apollo.game.model.def.NpcDefinition; +import org.apollo.util.ByteBufferUtil; + +/** + * Decodes npc data from the {@code npc.dat} file into {@link NpcDefinition}s. + * + * @author Major + */ +public final class NpcDefinitionDecoder { + + /** + * The {@link IndexedFileSystem}. + */ + private final IndexedFileSystem fs; + + /** + * Creates the npc definition decoder. + * + * @param fs The indexed file system. + */ + public NpcDefinitionDecoder(IndexedFileSystem fs) { + this.fs = fs; + } + + /** + * Decodes the npc definitions. + * + * @return An array of all parsed npc definitions. + * @throws IOException If an I/O error occurs. + */ + public NpcDefinition[] decode() throws IOException { + Archive config = Archive.decode(fs.getFile(0, 2)); + ByteBuffer data = config.getEntry("npc.dat").getBuffer(); + ByteBuffer idx = config.getEntry("npc.idx").getBuffer(); + + int count = idx.getShort(); + int[] indices = new int[count]; + int index = 2; + for (int i = 0; i < count; i++) { + indices[i] = index; + index += idx.getShort(); + } + + NpcDefinition[] defs = new NpcDefinition[count]; + for (int i = 0; i < count; i++) { + data.position(indices[i]); + defs[i] = decode(i, data); + } + + return defs; + } + + /** + * Decodes a single definition. + * + * @param id The npc's id. + * @param buffer The buffer. + * @return The {@link NpcDefinition}. + */ + private NpcDefinition decode(int id, ByteBuffer buffer) { + NpcDefinition definition = new NpcDefinition(id); + + while (true) { + int code = buffer.get() & 0xFF; + if (code == 0) { + return definition; + } else if (code == 1) { + int length = buffer.get() & 0xFF; + int[] npcModels = new int[length]; + for (int i = 0; i < length; i++) { + npcModels[i] = buffer.getShort(); + } + } else if (code == 2) { + definition.setName(ByteBufferUtil.readString(buffer)); + } else if (code == 3) { + definition.setDescription(ByteBufferUtil.readString(buffer)); + } else if (code == 12) { + definition.setSize(buffer.get()); + } else if (code == 13) { + definition.setStandAnimation(buffer.getShort()); + } else if (code == 14) { + definition.setWalkAnimation(buffer.getShort()); + } else if (code == 17) { + definition + .setWalkAnimations(buffer.getShort(), buffer.getShort(), buffer.getShort(), buffer.getShort()); + } else if (code >= 30 && code < 40) { + String str = ByteBufferUtil.readString(buffer); + if (str.equals("hidden")) { + str = null; + } + definition.setInteraction(code - 30, str); + } else if (code == 40) { + int length = buffer.get() & 0xFF; + int[] originalColours = new int[length]; + int[] replacementColours = new int[length]; + for (int i = 0; i < length; i++) { + originalColours[i] = buffer.getShort(); + replacementColours[i] = buffer.getShort(); + } + } else if (code == 60) { + int length = buffer.get() & 0xFF; + int[] additionalModels = new int[length]; + for (int i = 0; i < length; i++) { + additionalModels[i] = buffer.getShort(); + } + } else if (code == 90) { + buffer.getShort(); // Dummy + } else if (code == 91) { + buffer.getShort(); // Dummy + } else if (code == 92) { + buffer.getShort(); // Dummy + } else if (code == 93) { + @SuppressWarnings("unused") + boolean drawMinimapDot = false; + } else if (code == 95) { + definition.setCombatLevel(buffer.getShort()); + } else if (code == 97) { + @SuppressWarnings("unused") + int scaleXZ = buffer.getShort(); + } else if (code == 98) { + @SuppressWarnings("unused") + int scaleY = buffer.getShort(); + } else if (code == 99) { + @SuppressWarnings("unused") + boolean unknown = true; + } else if (code == 100) { + @SuppressWarnings("unused") + int lightModifier = buffer.get(); + } else if (code == 101) { + @SuppressWarnings("unused") + int shadowModifier = buffer.get() * 5; + } else if (code == 102) { + @SuppressWarnings("unused") + int headIcon = buffer.getShort(); + } else if (code == 103) { + @SuppressWarnings("unused") + int degreesToRotate = buffer.getShort(); + } else if (code == 106) { + int morphVariableBitsIndex = buffer.getShort(); + if (morphVariableBitsIndex == 65535) { + morphVariableBitsIndex = -1; + } + int morphismCount = buffer.getShort(); + if (morphismCount == 65535) { + morphismCount = -1; + } + + int count = buffer.get() & 0xFF; + int[] morphisms = new int[count + 1]; + for (int i = 0; i <= count; i++) { + int morphism = buffer.getShort(); + if (morphism == 65535) { + morphism = -1; + } + morphisms[i] = morphism; + } + } else if (code == 107) { + @SuppressWarnings("unused") + boolean clickable = false; + } + } + } + +} \ No newline at end of file diff --git a/src/org/apollo/fs/decoder/ObjectDefinitionDecoder.java b/src/org/apollo/fs/decoder/ObjectDefinitionDecoder.java new file mode 100644 index 00000000..31158dbf --- /dev/null +++ b/src/org/apollo/fs/decoder/ObjectDefinitionDecoder.java @@ -0,0 +1,161 @@ +package org.apollo.fs.decoder; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import org.apollo.fs.IndexedFileSystem; +import org.apollo.fs.archive.Archive; +import org.apollo.game.model.def.ObjectDefinition; +import org.apollo.util.ByteBufferUtil; + +/** + * Decodes object data from the {@code loc.dat} file into {@link ObjectDefinition}s. + * + * @author Major + */ +public final class ObjectDefinitionDecoder { + + /** + * The {@link IndexedFileSystem}. + */ + private final IndexedFileSystem fs; + + /** + * Creates the decoder. + * + * @param fs The {@link IndexedFileSystem}. + */ + public ObjectDefinitionDecoder(IndexedFileSystem fs) { + this.fs = fs; + } + + /** + * Decodes all of the data into {@link ObjectDefinition}s. + * + * @return The definitions. + * @throws IOException If an error occurs when decoding the archive or finding an entry. + */ + public ObjectDefinition[] decode() throws IOException { + Archive config = Archive.decode(fs.getFile(0, 2)); + ByteBuffer data = config.getEntry("loc.dat").getBuffer(); + ByteBuffer idx = config.getEntry("loc.idx").getBuffer(); + + int count = idx.getShort(); + int[] indices = new int[count]; + int index = 2; + for (int i = 0; i < count; i++) { + indices[i] = index; + index += idx.getShort(); + } + + ObjectDefinition[] defs = new ObjectDefinition[count]; + for (int i = 0; i < count; i++) { + data.position(indices[i]); + defs[i] = decode(i, data); + } + return defs; + } + + /** + * Decodes data from the cache into an {@link ObjectDefinition}. + * + * @param id The id of the object. + * @param data The {@link ByteBuffer} containing the data. + * @return The object definition. + */ + public ObjectDefinition decode(int id, ByteBuffer data) { + ObjectDefinition definition = new ObjectDefinition(id); + + while (true) { + int opcode = data.get() & 0xFF; + + if (opcode == 0) { + return definition; + } else if (opcode == 1) { + int amount = data.get() & 0xFF; + for (int i = 0; i < amount; i++) { + data.getShort(); // model id + data.get(); // model position + } + } else if (opcode == 2) { + definition.setName(ByteBufferUtil.readString(data)); + } else if (opcode == 3) { + definition.setDescription(ByteBufferUtil.readString(data)); + } else if (opcode == 5) { + int amount = data.get() & 0xFF; + for (int i = 0; i < amount; i++) { + data.getShort(); // model id + } + } else if (opcode == 14) { + definition.setWidth(data.get() & 0xFF); + } else if (opcode == 15) { + definition.setHeight(data.get() & 0xFF); + } else if (opcode == 17) { + definition.setSolid(false); + } else if (opcode == 18) { + definition.setImpenetrable(false); + } else if (opcode == 19) { + definition.setInteractive((data.get() & 0xFF) == 1); + } else if (opcode == 21) { + // boolean contouredGround = true; + } else if (opcode == 22) { + // boolean delayShading = true; + } else if (opcode == 23) { + // boolean occlues = true; + } else if (opcode == 24) { + data.getShort(); // animation + } else if (opcode == 28) { + data.get(); // decorDisplacement + } else if (opcode == 29) { + data.get(); // ambientLight + } else if (opcode >= 30 && opcode < 39) { + String[] actions = definition.getMenuActions(); + if (actions == null) { + actions = new String[10]; + } + String action = ByteBufferUtil.readString(data); + actions[opcode - 30] = action; + definition.setMenuActions(actions); + } else if (opcode == 39) { + data.get(); // light diffusion + } else if (opcode == 40) { + int amount = data.get() & 0xFF; + for (int i = 0; i < amount; i++) { + data.getShort(); // original colour + data.getShort(); // replacement colour + } + } else if (opcode == 60) { + data.getShort(); // minimap function + } else if (opcode == 62) { + // boolean inverted = true + } else if (opcode == 64) { + // boolean castsShadow = false; + } else if (opcode == 65) { + data.getShort(); // scale X + } else if (opcode == 66) { + data.getShort(); // scale Y + } else if (opcode == 67) { + data.getShort(); // scale Z + } else if (opcode == 68) { + data.getShort(); // map scene + } else if (opcode == 69) { + data.get(); // surroundings + } else if (opcode == 70) { + data.getShort(); // translate dX + } else if (opcode == 71) { + data.getShort(); // translate dY + } else if (opcode == 72) { + data.getShort(); // translate dZ + } else if (opcode == 73) { + // boolean obstructiveGround = true; + } else if (opcode == 74) { + // boolean ethereal = true; + } else if (opcode == 75) { + data.get(); // support items + } else { + continue; + } + } + } + +} \ No newline at end of file diff --git a/src/org/apollo/fs/decoder/StaticObjectDecoder.java b/src/org/apollo/fs/decoder/StaticObjectDecoder.java new file mode 100644 index 00000000..b3457011 --- /dev/null +++ b/src/org/apollo/fs/decoder/StaticObjectDecoder.java @@ -0,0 +1,119 @@ +package org.apollo.fs.decoder; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.apollo.fs.IndexedFileSystem; +import org.apollo.fs.archive.Archive; +import org.apollo.game.model.Position; +import org.apollo.game.model.obj.StaticObject; +import org.apollo.util.ByteBufferUtil; +import org.apollo.util.CompressionUtil; + +/** + * Decodes map object data from the {@code map_index.dat} file into {@link StaticObject}s. + * + * @author Chris Fletcher + */ +public final class StaticObjectDecoder { + + /** + * The {@link IndexedFileSystem}. + */ + private final IndexedFileSystem fs; + + /** + * Creates the decoder. + * + * @param fs The indexed file system. + */ + public StaticObjectDecoder(IndexedFileSystem fs) { + this.fs = fs; + } + + /** + * Decodes all static objects and places them in the returned array. + * + * @return The decoded objects. + * @throws IOException If an I/O error occurs. + */ + public StaticObject[] decode() throws IOException { + Archive versionList = Archive.decode(fs.getFile(0, 5)); + ByteBuffer buffer = versionList.getEntry("map_index").getBuffer(); + + int indices = buffer.remaining() / 7; + int[] areas = new int[indices]; + int[] landscapes = new int[indices]; + + for (int i = 0; i < indices; i++) { + areas[i] = buffer.getShort() & 0xFFFF; + + @SuppressWarnings("unused") + int mapFile = buffer.getShort() & 0xFFFF; + + landscapes[i] = buffer.getShort() & 0xFFFF; + + @SuppressWarnings("unused") + boolean members = (buffer.get() & 0xFF) == 1; + } + + List objects = new ArrayList(); + + for (int i = 0; i < indices; i++) { + ByteBuffer compressed = fs.getFile(4, landscapes[i]); + ByteBuffer uncompressed = ByteBuffer.wrap(CompressionUtil.ungzip(compressed)); + + Collection areaObjects = parseArea(areas[i], uncompressed); + objects.addAll(areaObjects); + } + + return objects.toArray(new StaticObject[objects.size()]); + } + + /** + * Parses a single area from the specified buffer. + * + * @param area The identifier of that area. + * @param buffer The buffer which holds the area's data. + * @return A collection of all parsed objects. + */ + private Collection parseArea(int area, ByteBuffer buffer) { + List objects = new ArrayList(); + + int x = (area >> 8 & 0xFF) * 64; + int y = (area & 0xFF) * 64; + + int id = -1; + int idOffset; + + while ((idOffset = ByteBufferUtil.readSmart(buffer)) != 0) { + id += idOffset; + + int position = 0; + int positionOffset; + + while ((positionOffset = ByteBufferUtil.readSmart(buffer)) != 0) { + position += positionOffset - 1; + + int localX = position >> 6 & 0x3F; + int localY = position & 0x3F; + int height = position >> 12; + + int info = buffer.get() & 0xFF; + int type = info >> 2; + int rotation = info & 3; + + Position pos = new Position(x + localX, y + localY, height); + + StaticObject object = new StaticObject(id, pos, type, rotation); + objects.add(object); + } + } + + return objects; + } + +} \ No newline at end of file diff --git a/src/org/apollo/fs/parser/package-info.java b/src/org/apollo/fs/decoder/package-info.java similarity index 69% rename from src/org/apollo/fs/parser/package-info.java rename to src/org/apollo/fs/decoder/package-info.java index e3ac1a67..fb9c07ce 100644 --- a/src/org/apollo/fs/parser/package-info.java +++ b/src/org/apollo/fs/decoder/package-info.java @@ -1,5 +1,5 @@ /** * Contains classes which parse files within the game's cache. */ -package org.apollo.fs.parser; +package org.apollo.fs.decoder; diff --git a/src/org/apollo/game/event/handler/impl/EquipEventHandler.java b/src/org/apollo/game/event/handler/impl/EquipEventHandler.java index 8996cc87..b1d315d7 100644 --- a/src/org/apollo/game/event/handler/impl/EquipEventHandler.java +++ b/src/org/apollo/game/event/handler/impl/EquipEventHandler.java @@ -8,9 +8,7 @@ import org.apollo.game.model.Inventory; import org.apollo.game.model.Item; import org.apollo.game.model.Player; import org.apollo.game.model.Skill; -import org.apollo.game.model.SkillSet; import org.apollo.game.model.def.EquipmentDefinition; -import org.apollo.game.model.def.ItemDefinition; import org.apollo.game.model.inv.SynchronizationInventoryListener; import org.apollo.util.LanguageUtil; diff --git a/src/org/apollo/game/model/Npc.java b/src/org/apollo/game/model/Npc.java new file mode 100644 index 00000000..020fde5c --- /dev/null +++ b/src/org/apollo/game/model/Npc.java @@ -0,0 +1,52 @@ +package org.apollo.game.model; + +import org.apollo.game.event.Event; +import org.apollo.game.model.def.NpcDefinition; + +/** + * An {@link Npc} is a {@link Character} that is not being controlled by a player. + * + * @author Major + */ +public class Npc extends Character { + + /** + * This npc's definition. + */ + private final NpcDefinition definition; + + /** + * Creates a new npc with the specified id and {@link Position}. + * + * @param id The id. + * @param position The position. + */ + public Npc(int id, Position position) { + this(NpcDefinition.forId(id), position); + } + + /** + * Creates a new npc with the specified {@link NpcDefinition} and {@link Position}. + * + * @param definition The definition. + * @param position The position. + */ + public Npc(NpcDefinition definition, Position position) { + super(position); + this.definition = definition; + } + + /** + * Gets this npc's {@link NpcDefinition} + * + * @return The definition. + */ + public NpcDefinition getDefinition() { + return definition; + } + + @Override + public void send(Event event) { + } + +} \ 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 297cf6bf..bf390254 100644 --- a/src/org/apollo/game/model/World.java +++ b/src/org/apollo/game/model/World.java @@ -8,10 +8,16 @@ import java.util.logging.Logger; import org.apollo.Service; import org.apollo.fs.IndexedFileSystem; -import org.apollo.fs.parser.ItemDefinitionParser; +import org.apollo.fs.decoder.ItemDefinitionDecoder; +import org.apollo.fs.decoder.NpcDefinitionDecoder; +import org.apollo.fs.decoder.ObjectDefinitionDecoder; +import org.apollo.fs.decoder.StaticObjectDecoder; import org.apollo.game.command.CommandDispatcher; import org.apollo.game.model.def.EquipmentDefinition; import org.apollo.game.model.def.ItemDefinition; +import org.apollo.game.model.def.NpcDefinition; +import org.apollo.game.model.def.ObjectDefinition; +import org.apollo.game.model.obj.StaticObject; import org.apollo.game.scheduling.ScheduledTask; import org.apollo.game.scheduling.Scheduler; import org.apollo.io.EquipmentDefinitionParser; @@ -102,34 +108,42 @@ public final class World { * * @param release The release number. * @param fs The file system. - * @param mgr The plugin manager. TODO move this. + * @param manager The plugin manager. TODO move this. * @throws IOException If an I/O error occurs. */ - public void init(int release, IndexedFileSystem fs, PluginManager mgr) throws IOException { - logger.info("Loading item definitions..."); - ItemDefinitionParser itemParser = new ItemDefinitionParser(fs); - ItemDefinition[] itemDefs = itemParser.parse(); - ItemDefinition.init(itemDefs); - logger.info("Done (loaded " + itemDefs.length + " item definitions)."); + public void init(int release, IndexedFileSystem fs, PluginManager manager) throws IOException { + + ItemDefinitionDecoder itemParser = new ItemDefinitionDecoder(fs); + ItemDefinition[] itemDefs = itemParser.decode(); + ItemDefinition.init(itemDefs); + logger.info("Loaded " + itemDefs.length + " item definitions."); - logger.info("Loading equipment definitions..."); - int nonNull = 0; InputStream is = new BufferedInputStream(new FileInputStream("data/equipment-" + release + ".dat")); try { - EquipmentDefinitionParser equipParser = new EquipmentDefinitionParser(is); - EquipmentDefinition[] equipDefs = equipParser.parse(); - for (EquipmentDefinition def : equipDefs) { - if (def != null) { - nonNull++; - } - } - EquipmentDefinition.init(equipDefs); + EquipmentDefinitionParser parser = new EquipmentDefinitionParser(is); + EquipmentDefinition[] defs = parser.parse(); + EquipmentDefinition.init(defs); + logger.info("Loaded " + defs.length + " equipment definitions."); } finally { is.close(); } - logger.info("Done (loaded " + nonNull + " equipment definitions)."); - this.pluginManager = mgr; // TODO move!! + NpcDefinitionDecoder parser = new NpcDefinitionDecoder(fs); + NpcDefinition[] npcDefs = parser.decode(); + NpcDefinition.init(npcDefs); + logger.info("Loaded " + npcDefs.length + " npc definitions."); + + ObjectDefinitionDecoder objParser = new ObjectDefinitionDecoder(fs); + ObjectDefinition[] objDefs = objParser.decode(); + ObjectDefinition.init(objDefs); + logger.info("Loaded " + objDefs.length + " object definitions."); + + StaticObjectDecoder objectParser = new StaticObjectDecoder(fs); + StaticObject[] objects = objectParser.decode(); + StaticObject.init(objects); + logger.info("Loaded " + objects.length + " static objects."); + + pluginManager = manager; // TODO move!! } /** diff --git a/src/org/apollo/game/model/def/ItemDefinition.java b/src/org/apollo/game/model/def/ItemDefinition.java index 7a3fc5de..ee678d84 100644 --- a/src/org/apollo/game/model/def/ItemDefinition.java +++ b/src/org/apollo/game/model/def/ItemDefinition.java @@ -128,6 +128,11 @@ public final class ItemDefinition { */ private boolean members; + /** + * This item's team. + */ + private int team; + /** * The ground actions array. */ @@ -387,4 +392,22 @@ public final class ItemDefinition { return inventoryActions[id]; } -} + /** + * Sets this items team. + * + * @param team The team. + */ + public void setTeam(int team) { + this.team = team; + } + + /** + * Gets this item's team. + * + * @return The team. + */ + public int getTeam() { + return team; + } + +} \ No newline at end of file diff --git a/src/org/apollo/game/model/def/NpcDefinition.java b/src/org/apollo/game/model/def/NpcDefinition.java new file mode 100644 index 00000000..2d8d6079 --- /dev/null +++ b/src/org/apollo/game/model/def/NpcDefinition.java @@ -0,0 +1,362 @@ +package org.apollo.game.model.def; + +/** + * Represents a type of {@link Npc}. + * + * @author Chris Fletcher + */ +public final class NpcDefinition { + + /** + * The NPC definitions. + */ + private static NpcDefinition[] definitions; + + /** + * Initialises the class with the specified set of definitions. + * + * @param definitions The definitions. + * @throws RuntimeException If there is an id mismatch. + */ + public static void init(NpcDefinition[] definitions) { + NpcDefinition.definitions = definitions; + for (int id = 0; id < definitions.length; id++) { + NpcDefinition def = definitions[id]; + if (def.getId() != id) { + throw new RuntimeException("NPC definition id mismatch"); + } + } + } + + /** + * Gets the total number of NPCs. + * + * @return The total number of NPCs. + */ + public static int count() { + return definitions.length; + } + + /** + * Gets the NPC definition for the specified id. + * + * @param The id. + * @return The definition. + * @throws IndexOutOfBoundsException If the id is out of bounds. + */ + public static NpcDefinition forId(int id) { + if (id < 0 || id >= definitions.length) { + throw new IndexOutOfBoundsException(); + } + return definitions[id]; + } + + /** + * The NPC id. + */ + private final int id; + + /** + * The name of the NPC. + */ + private String name; + + /** + * The description of the NPC. + */ + private String description; + + /** + * The NPC's size, in tiles. + */ + private int size = 1; + + /** + * The various animation ids. + */ + private int standAnim = -1, walkAnim = -1, walkBackAnim = -1, walkLeftAnim = -1, walkRightAnim = -1; + + /** + * An array of interaction options. + */ + private String[] interactions = new String[5]; + + /** + * The combat level of the NPC. + */ + private int combatLevel = -1; + + /** + * Creates a new NPC definition. + * + * @param id The NPC id. + */ + public NpcDefinition(int id) { + this.id = id; + } + + /** + * Gets the NPC id. + * + * @return The NPC id. + */ + public int getId() { + return id; + } + + /** + * Gets the name of the NPC. + * + * @return The name of the NPC. + */ + public String getName() { + return name; + } + + /** + * Gets the description of the NPC. + * + * @return The description. + */ + public String getDescription() { + return description; + } + + /** + * Gets the NPC's size, in tiles. + * + * @return The size. + */ + public int getSize() { + return size; + } + + /** + * Gets the id of the NPC's standing animation. + * + * @return The stand animation id, or -1 if it doesn't have one. + */ + public int getStandAnimation() { + return standAnim; + } + + /** + * Gets the walking animation of the NPC. + * + * @return The walking animation. + */ + public int getWalkAnimation() { + return walkAnim; + } + + /** + * Gets the walk-back animation of the NPC. + * + * @return The walk-back animation. + */ + public int getWalkBackAnimation() { + return walkBackAnim; + } + + /** + * Gets the walk-left animation of the NPC. + * + * @return The walk-left animation. + */ + public int getWalkLeftAnimation() { + return walkLeftAnim; + } + + /** + * Gets the walk-right animation of the NPC. + * + * @return The walk-right animation. + */ + public int getWalkRightAnimation() { + return walkRightAnim; + } + + /** + * Gets an interaction option. + * + * @param slot The slot of the option. + * @return The option, or {@code null} if there isn't any at the specified slot. + * @throws IndexOutOfBoundsException If the slot is out of bounds. + */ + public String getInteraction(int slot) { + if (slot < 0 || slot >= interactions.length) { + throw new IndexOutOfBoundsException(); + } + return interactions[slot]; + } + + /** + * Gets the array of interaction options. + * + * @return The interaction options. + */ + public String[] getInteractions() { + return interactions; + } + + /** + * Gets the NPC's combat level. + * + * @return The combat level, or -1 if it doesn't have one. + */ + public int getCombatLevel() { + return combatLevel; + } + + /** + * Checks if the NPC has a standing animation id. + * + * @return {@code true} if so, {@code false} if not. + */ + public boolean hasStandAnimation() { + return standAnim != -1; + } + + /** + * Checks if the NPC has a walking animation. + * + * @return {@code true} if so, {@code false} if not. + */ + public boolean hasWalkAnimation() { + return walkAnim != -1; + } + + /** + * Checks if the NPC has a walk-back animation. + * + * @return {@code true} if so, {@code false} if not. + */ + public boolean hasWalkBackAnimation() { + return walkBackAnim != -1; + } + + /** + * Checks if the NPC has a walk-left animation. + * + * @return {@code true} if so, {@code false} if not. + */ + public boolean hasWalkLeftAnimation() { + return walkLeftAnim != -1; + } + + /** + * Checks if the NPC has a walk-right animation. + * + * @return {@code true} if so, {@code false} if not. + */ + public boolean hasWalkRightAnimation() { + return walkRightAnim != -1; + } + + /** + * Checks if there is an interaction option present. + * + * @param slot The slot to check. + * @return {@code true} if so, {@code false} if not. + * @throws IndexOutOfBoundsException If the slot is out of bounds. + */ + public boolean hasInteraction(int slot) { + if (slot < 0 || slot >= interactions.length) { + throw new IndexOutOfBoundsException(); + } + return interactions[slot] != null; + } + + /** + * Checks if the NPC has a combat level. + * + * @return {@code true} if so, {@code false} if not. + */ + public boolean hasCombatLevel() { + return combatLevel != -1; + } + + /** + * Sets the name of the NPC. + * + * @param name The name. + */ + public void setName(String name) { + this.name = name; + } + + /** + * Sets the description of the NPC. + * + * @param description The description. + */ + public void setDescription(String description) { + this.description = description; + } + + /** + * Sets the size of the NPC, in tiles. + * + * @param size The size. + */ + public void setSize(int size) { + this.size = size; + } + + /** + * Sets the id of the NPC's standing animation. + * + * @param standAnim The stand animation id. + */ + public void setStandAnimation(int standAnim) { + this.standAnim = standAnim; + } + + /** + * Sets the walking animation of the NPC. + * + * @param walkAnim The walking animation. + */ + public void setWalkAnimation(int walkAnim) { + this.walkAnim = walkAnim; + } + + /** + * Sets the various walking animations of the NPC. + * + * @param walkAnim The walking animation. + * @param walkBackAnim The walk-back animation. + * @param walkLeftAnim The walk-left animation. + * @param walkRightAnim The walk-right animation. + */ + public void setWalkAnimations(int walkAnim, int walkBackAnim, int walkLeftAnim, int walkRightAnim) { + this.walkAnim = walkAnim; + this.walkBackAnim = walkBackAnim; + this.walkLeftAnim = walkLeftAnim; + this.walkRightAnim = walkRightAnim; + } + + /** + * Sets an interaction option. + * + * @param slot The slot of the option. + * @param interaction The interaction options. + * @throws IndexOutOfBoundsException If the slot is out of bounds. + */ + public void setInteraction(int slot, String interaction) { + if (slot < 0 || slot >= interactions.length) { + throw new IndexOutOfBoundsException(); + } + interactions[slot] = interaction; + } + + /** + * Sets the NPC's combat level. + * + * @param combatLevel The combat level. + */ + public void setCombatLevel(int combatLevel) { + this.combatLevel = combatLevel; + } + +} \ No newline at end of file diff --git a/src/org/apollo/game/model/def/ObjectDefinition.java b/src/org/apollo/game/model/def/ObjectDefinition.java new file mode 100644 index 00000000..eefba402 --- /dev/null +++ b/src/org/apollo/game/model/def/ObjectDefinition.java @@ -0,0 +1,272 @@ +package org.apollo.game.model.def; + +import org.apollo.game.model.obj.GameObject; + +/** + * Represents a type of {@link GameObject}. + * + * @author Major + */ +public final class ObjectDefinition { + + /** + * Gets the total number of objects. + * + * @return The total number of objects. + */ + public static int count() { + return definitions.length; + } + + /** + * Gets the array of object definitions. + * + * @return The definitions. + */ + public static ObjectDefinition[] getDefinitions() { + return definitions; + } + + /** + * Initialises the object definitions. + * + * @param definitions The decoded definitions. + */ + public static void init(ObjectDefinition[] definitions) { + ObjectDefinition.definitions = definitions; + for (int id = 0; id < definitions.length; id++) { + // This also verifies that none of the definitions are null. + ObjectDefinition def = definitions[id]; + if (def.getId() != id) { + throw new RuntimeException("Item definition id mismatch!"); + } + } + } + + /** + * Gets the object definition for the specified id. + * + * @param id The id of the object. + * @return The definition. + */ + public static ObjectDefinition lookup(int id) { + if (id < 0 || id > definitions.length) { + throw new IndexOutOfBoundsException(ObjectDefinition.class.getName() + " lookup index " + id + + " out of bounds."); + } + return definitions[id]; + } + + /** + * The object's menu actions. + */ + private String[] menuActions; + + /** + * The object's description. + */ + private String description; + + /** + * Denotes whether this object has actions associated with it or not. + */ + private boolean interactive; + + /** + * The object's id. + */ + private final int id; + + /** + * The object's name. + */ + private String name; + + /** + * Denotes whether this object is impenetrable or not. + */ + private boolean impenetrable; + + /** + * Denotes whether the object can be walked over or not. + */ + private boolean solid; + + /** + * This object's height. + */ + private int height; + + /** + * This object's width. + */ + private int width; + + /** + * The array of game object definitions. + */ + private static ObjectDefinition[] definitions; + + /** + * Creates a new object definition. + * + * @param id The id of the object. + */ + public ObjectDefinition(int id) { + this.id = id; + } + + /** + * Gets the description of this object. + * + * @return The description. + */ + public String getDescription() { + return description; + } + + /** + * Gets the height of this object. + * + * @return The height. + */ + public int getHeight() { + return width; + } + + /** + * Gets the id of this object. + * + * @return The id. + */ + public int getId() { + return id; + } + + /** + * Gets the menu actions of this object. + * + * @return The menu actions. + */ + public String[] getMenuActions() { + return menuActions; + } + + /** + * Gets the name of this object. + * + * @return The name. + */ + public String getName() { + return name; + } + + /** + * Gets the with of this object. + * + * @return The width. + */ + public int getWidth() { + return height; + } + + /** + * Indicates the impenetrability of this object. + * + * @return {@code true} if this object is impenetrable, otherwise {@code false}. + */ + public boolean isImpenetrable() { + return impenetrable; + } + + /** + * Indicates the interactivity of this object. + * + * @return {@code true} if the object is interactive, otherwise {@code false}. + */ + public boolean isInteractive() { + return interactive; + } + + /** + * Indicates the solidity of this object. + * + * @return {@code true} if this object is solid, otherwise {@code false}. + */ + public boolean isSolid() { + return solid; + } + + /** + * Sets the description of this object. + * + * @param description The description. + */ + public void setDescription(String description) { + this.description = description; + } + + /** + * Sets the height of this object. + * + * @param height The height. + */ + public void setHeight(int height) { + this.height = height; + } + + /** + * Sets the impenetrability of this object. + * + * @param impenetrable The impenetrability. + */ + public void setImpenetrable(boolean impenetrable) { + this.impenetrable = impenetrable; + } + + /** + * Sets the interactivity of this object. + * + * @param interactive The interactivity. + */ + public void setInteractive(boolean interactive) { + this.interactive = interactive; + } + + /** + * Sets the menu actions of this object. + * + * @param menuActions The menu actions. + */ + public void setMenuActions(String[] menuActions) { + this.menuActions = menuActions; + } + + /** + * Sets the name of this object. + * + * @param name The name. + */ + public void setName(String name) { + this.name = name; + } + + /** + * Sets the solidity of this object. + * + * @param solid The solidity. + */ + public void setSolid(boolean solid) { + this.solid = solid; + } + + /** + * Sets the width of this object. + * + * @param width The width. + */ + public void setWidth(int width) { + this.width = width; + } + +} \ No newline at end of file diff --git a/src/org/apollo/game/model/obj/GameObject.java b/src/org/apollo/game/model/obj/GameObject.java new file mode 100644 index 00000000..e21bc697 --- /dev/null +++ b/src/org/apollo/game/model/obj/GameObject.java @@ -0,0 +1,35 @@ +package org.apollo.game.model.obj; + +import org.apollo.game.model.def.ObjectDefinition; + +/** + * Represents an in-game object. + * + * @author Major + */ +public final class GameObject { + + /** + * The object definition. + */ + private final ObjectDefinition definition; + + /** + * Creates the game object. + * + * @param definition The object's definition. + */ + public GameObject(ObjectDefinition definition) { + this.definition = definition; + } + + /** + * Gets the object's definition. + * + * @return The definition. + */ + public ObjectDefinition getDefinition() { + return definition; + } + +} \ No newline at end of file diff --git a/src/org/apollo/game/model/obj/StaticObject.java b/src/org/apollo/game/model/obj/StaticObject.java index 4201b22f..a5e5e345 100644 --- a/src/org/apollo/game/model/obj/StaticObject.java +++ b/src/org/apollo/game/model/obj/StaticObject.java @@ -1,35 +1,112 @@ package org.apollo.game.model.obj; -import org.apollo.game.model.def.StaticObjectDefinition; +import org.apollo.game.model.Position; +import org.apollo.game.model.def.ObjectDefinition; /** * Represents a static object in the game world. * * @author Graham + * @author Chris Fletcher + * @author Major */ public final class StaticObject { /** - * The object definition. + * Initialises the static objects. */ - private final StaticObjectDefinition definition; - - /** - * Creates the game object. - * - * @param definition The object's definition. - */ - public StaticObject(StaticObjectDefinition definition) { - this.definition = definition; + public static void init(StaticObject[] objects) { } /** - * Gets the object's definition. - * - * @return The definition. + * The object's id. */ - public StaticObjectDefinition getDefinition() { + private final short id; + + /** + * The object's definition. + */ + private final ObjectDefinition definition; + + /** + * The object's position. + */ + private final Position position; + + /** + * The object type. + */ + private final byte type; + + /** + * The object's rotation. + */ + private final byte rotation; + + /** + * Creates a new static object. + * + * @param def The object's id. + * @param position The position. + * @param type The type code of the object. + * @param rotation The rotation of the object. + */ + public StaticObject(int id, Position position, int type, int rotation) { + this.id = (short) id; + this.position = position; + this.type = (byte) type; + this.rotation = (byte) rotation; + this.definition = ObjectDefinition.lookup(id); + } + + /** + * Gets the definition of this object. + * + * @return The object's definition. + */ + public ObjectDefinition getDefinition() { return definition; } + /** + * Gets the id of the object. + * + * @return The object id. + */ + public int getId() { + return id; + } + + /** + * Gets the position of this object. + * + * @return The object's position. + */ + public Position getPosition() { + return position; + } + + /** + * Gets the object's rotation. + * + * @return The rotation. + */ + public int getRotation() { + return rotation; + } + + /** + * Gets the type code of the object. + * + * @return The type. + */ + public int getType() { + return type; + } + + @Override + public String toString() { + return StaticObject.class.getName() + " [id= " + id + ", type= " + type + ", rotation= " + rotation + "]"; + } + } \ No newline at end of file diff --git a/src/org/apollo/tools/EquipmentUpdater.java b/src/org/apollo/tools/EquipmentUpdater.java index 6e92ed8b..322bd3b3 100644 --- a/src/org/apollo/tools/EquipmentUpdater.java +++ b/src/org/apollo/tools/EquipmentUpdater.java @@ -6,7 +6,7 @@ import java.io.File; import java.io.FileOutputStream; import org.apollo.fs.IndexedFileSystem; -import org.apollo.fs.parser.ItemDefinitionParser; +import org.apollo.fs.decoder.ItemDefinitionDecoder; import org.apollo.game.model.def.ItemDefinition; /** @@ -36,8 +36,8 @@ public final class EquipmentUpdater { try { IndexedFileSystem fs = new IndexedFileSystem(new File("data/fs/" + release), true); try { - ItemDefinitionParser parser = new ItemDefinitionParser(fs); - ItemDefinition[] defs = parser.parse(); + ItemDefinitionDecoder decoder = new ItemDefinitionDecoder(fs); + ItemDefinition[] defs = decoder.decode(); ItemDefinition.init(defs); os.writeShort(defs.length); diff --git a/src/org/apollo/tools/NoteUpdater.java b/src/org/apollo/tools/NoteUpdater.java index 599d240d..fccfa20f 100644 --- a/src/org/apollo/tools/NoteUpdater.java +++ b/src/org/apollo/tools/NoteUpdater.java @@ -8,7 +8,7 @@ import java.util.HashMap; import java.util.Map; import org.apollo.fs.IndexedFileSystem; -import org.apollo.fs.parser.ItemDefinitionParser; +import org.apollo.fs.decoder.ItemDefinitionDecoder; import org.apollo.game.model.def.ItemDefinition; /** @@ -37,8 +37,8 @@ public final class NoteUpdater { try { IndexedFileSystem fs = new IndexedFileSystem(new File("data/fs/" + release), true); try { - ItemDefinitionParser parser = new ItemDefinitionParser(fs); - ItemDefinition[] defs = parser.parse(); + ItemDefinitionDecoder decoder = new ItemDefinitionDecoder(fs); + ItemDefinition[] defs = decoder.decode(); ItemDefinition.init(defs); os.writeShort(defs.length); diff --git a/src/org/apollo/util/ByteBufferUtil.java b/src/org/apollo/util/ByteBufferUtil.java index 4df53189..8e4ee64f 100644 --- a/src/org/apollo/util/ByteBufferUtil.java +++ b/src/org/apollo/util/ByteBufferUtil.java @@ -12,13 +12,17 @@ import org.apollo.net.NetworkConstants; public final class ByteBufferUtil { /** - * Reads an unsigned tri byte from the specified buffer. + * Reads a 'smart' (either a {@code byte} or {@code short} depending on the value) from the specified buffer. * * @param buffer The buffer. - * @return The tri byte. + * @return The 'smart'. */ - public static int readUnsignedTriByte(ByteBuffer buffer) { - return ((buffer.get() & 0xFF) << 16) | ((buffer.get() & 0xFF) << 8) | (buffer.get() & 0xFF); + public static int readSmart(ByteBuffer buffer) { + int peek = buffer.get(buffer.position()) & 0xFF; + if (peek < 128) { + return buffer.get() & 0xFF; + } + return (buffer.getShort() & 0xFFFF) - 32768; } /** @@ -36,6 +40,16 @@ public final class ByteBufferUtil { return bldr.toString(); } + /** + * Reads an unsigned tri byte from the specified buffer. + * + * @param buffer The buffer. + * @return The tri byte. + */ + public static int readUnsignedTriByte(ByteBuffer buffer) { + return ((buffer.get() & 0xFF) << 16) | ((buffer.get() & 0xFF) << 8) | (buffer.get() & 0xFF); + } + /** * Default private constructor to prevent instantiation. */ diff --git a/src/org/apollo/util/CompressionUtil.java b/src/org/apollo/util/CompressionUtil.java index e248f1e7..7409c37d 100644 --- a/src/org/apollo/util/CompressionUtil.java +++ b/src/org/apollo/util/CompressionUtil.java @@ -4,6 +4,8 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; import java.util.zip.DeflaterOutputStream; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; @@ -34,6 +36,39 @@ public final class CompressionUtil { } } + /** + * Ungzips the compressed buffer and places the results into the returning array. + * + * @param compressed The compressed buffer. + * @return The uncompressed array. + * @throws IOException If an I/O error occurs. + */ + public static byte[] ungzip(ByteBuffer compressed) throws IOException { + byte[] data = new byte[compressed.remaining()]; + compressed.get(data); + InputStream is = new GZIPInputStream(new ByteArrayInputStream(data)); + + try { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + try { + while (true) { + byte[] buf = new byte[1024]; + int read = is.read(buf, 0, buf.length); + if (read == -1) { + break; + } + os.write(buf, 0, read); + } + } finally { + os.close(); + } + + return os.toByteArray(); + } finally { + is.close(); + } + } + /** * Unbzip2s the compressed array and places the result into the uncompressed array. *