From b95c48b2e34c7bf390418ba2624fc6865d729584 Mon Sep 17 00:00:00 2001 From: Major- Date: Wed, 25 Feb 2015 16:32:19 +0000 Subject: [PATCH 1/9] Add HintIconMessage, FourthNpcActionMessage, and FifthNpcActionMessage. --- .../message/impl/FifthNpcActionMessage.java | 19 ++++ .../message/impl/FourthNpcActionMessage.java | 19 ++++ .../game/message/impl/HintIconMessage.java | 102 ++++++++++++++++++ .../release/r317/HintIconMessageEncoder.java | 34 ++++++ .../apollo/net/release/r317/Release317.java | 3 + .../release/r377/HintIconMessageEncoder.java | 34 ++++++ .../apollo/net/release/r377/Release377.java | 7 +- 7 files changed, 216 insertions(+), 2 deletions(-) create mode 100644 src/org/apollo/game/message/impl/FifthNpcActionMessage.java create mode 100644 src/org/apollo/game/message/impl/FourthNpcActionMessage.java create mode 100644 src/org/apollo/game/message/impl/HintIconMessage.java create mode 100644 src/org/apollo/net/release/r317/HintIconMessageEncoder.java create mode 100644 src/org/apollo/net/release/r377/HintIconMessageEncoder.java diff --git a/src/org/apollo/game/message/impl/FifthNpcActionMessage.java b/src/org/apollo/game/message/impl/FifthNpcActionMessage.java new file mode 100644 index 00000000..6dddd036 --- /dev/null +++ b/src/org/apollo/game/message/impl/FifthNpcActionMessage.java @@ -0,0 +1,19 @@ +package org.apollo.game.message.impl; + +/** + * The fifth {@link NpcActionMessage}. + * + * @author Major + */ +public final class FifthNpcActionMessage extends NpcActionMessage { + + /** + * Creates a new fifth npc action message. + * + * @param index The index of the npc. + */ + public FifthNpcActionMessage(int index) { + super(5, index); + } + +} \ No newline at end of file diff --git a/src/org/apollo/game/message/impl/FourthNpcActionMessage.java b/src/org/apollo/game/message/impl/FourthNpcActionMessage.java new file mode 100644 index 00000000..2ecd1ae1 --- /dev/null +++ b/src/org/apollo/game/message/impl/FourthNpcActionMessage.java @@ -0,0 +1,19 @@ +package org.apollo.game.message.impl; + +/** + * The fourth {@link NpcActionMessage}. + * + * @author Major + */ +public final class FourthNpcActionMessage extends NpcActionMessage { + + /** + * Creates a new fourth npc action message. + * + * @param index The index of the npc. + */ + public FourthNpcActionMessage(int index) { + super(4, index); + } + +} \ No newline at end of file diff --git a/src/org/apollo/game/message/impl/HintIconMessage.java b/src/org/apollo/game/message/impl/HintIconMessage.java new file mode 100644 index 00000000..dff7f252 --- /dev/null +++ b/src/org/apollo/game/message/impl/HintIconMessage.java @@ -0,0 +1,102 @@ +package org.apollo.game.message.impl; + +import java.util.Optional; + +import org.apollo.game.message.Message; +import org.apollo.game.model.Position; + +/** + * A {@link Message} that displays a hint icon over an Npc, tile, or player. + * + * @author Major + */ +public final class HintIconMessage extends Message { + + // TODO identify the other types and use an enum. + + /** + * The hint icon type for Npcs. + */ + public static final int NPC_TYPE = 1; + + /** + * The hint icon type for Players. + */ + public static final int PLAYER_TYPE = 10; + + /** + * Creates a HintIconMessage for the Npc with the specified index. + * + * @param index The index of the Npc. + * @return The HintIconMessage. + */ + public static HintIconMessage forNpc(int index) { + return new HintIconMessage(NPC_TYPE, Optional.of(index), Optional.empty()); + } + + /** + * Creates a HintIconMessage for the Player with the specified index. + * + * @param index The index of the Player. + * @return The HintIconMessage. + */ + public static HintIconMessage forPlayer(int index) { + return new HintIconMessage(PLAYER_TYPE, Optional.of(index), Optional.empty()); + } + + /** + * The index of the entity, if applicable. + */ + private final Optional index; + + /** + * The Position of the tile, if applicable. + */ + private final Optional position; + + /** + * The type of entity this HintIconMessage is directed at. + */ + private final int type; + + /** + * Creates the HintIconMessage. + * + * @param type The type of entity this HintIconMessage is directed at. + * @param index The index of the entity, if applicable. + * @param position The Position of the tile, if applicable. + */ + private HintIconMessage(int type, Optional index, Optional position) { + this.type = type; + this.index = index; + this.position = position; + } + + /** + * Gets the index of the entity, if applicable. + * + * @return The index. + */ + public Optional getIndex() { + return index; + } + + /** + * Gets the {@link Position} of the tile, if applicable. + * + * @return The Position. + */ + public Optional getPosition() { + return position; + } + + /** + * Gets the type this HintIconMessage is directed at. + * + * @return The type. + */ + public int getType() { + return type; + } + +} \ No newline at end of file diff --git a/src/org/apollo/net/release/r317/HintIconMessageEncoder.java b/src/org/apollo/net/release/r317/HintIconMessageEncoder.java new file mode 100644 index 00000000..b8be56c3 --- /dev/null +++ b/src/org/apollo/net/release/r317/HintIconMessageEncoder.java @@ -0,0 +1,34 @@ +package org.apollo.net.release.r317; + +import org.apollo.game.message.impl.HintIconMessage; +import org.apollo.net.codec.game.DataType; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketBuilder; +import org.apollo.net.release.MessageEncoder; + +/** + * A {@link MessageEncoder} for the {@link HintIconMessage}. + * + * @author Major + */ +public final class HintIconMessageEncoder extends MessageEncoder { + + @Override + public GamePacket encode(HintIconMessage message) { + GamePacketBuilder builder = new GamePacketBuilder(254); + int type = message.getType(); + builder.put(DataType.BYTE, type); + + switch (type) { + case HintIconMessage.NPC_TYPE: + case HintIconMessage.PLAYER_TYPE: + builder.put(DataType.SHORT, message.getIndex().get()); + break; + default: + throw new IllegalStateException("Unsupported hint icon type " + type + "."); + } + + return builder.toGamePacket(); + } + +} \ No newline at end of file diff --git a/src/org/apollo/net/release/r317/Release317.java b/src/org/apollo/net/release/r317/Release317.java index 4d4a48ea..b90d0308 100644 --- a/src/org/apollo/net/release/r317/Release317.java +++ b/src/org/apollo/net/release/r317/Release317.java @@ -9,6 +9,7 @@ import org.apollo.game.message.impl.DisplayTabInterfaceMessage; import org.apollo.game.message.impl.EnterAmountMessage; import org.apollo.game.message.impl.ForwardPrivateChatMessage; import org.apollo.game.message.impl.FriendServerStatusMessage; +import org.apollo.game.message.impl.HintIconMessage; import org.apollo.game.message.impl.IdAssignmentMessage; import org.apollo.game.message.impl.IgnoreListMessage; import org.apollo.game.message.impl.LogoutMessage; @@ -145,6 +146,7 @@ public final class Release317 extends Release { register(155, new FirstNpcActionMessageDecoder()); register(17, new SecondNpcActionMessageDecoder()); register(21, new ThirdNpcActionMessageDecoder()); + register(236, new TakeTileItemMessageDecoder()); register(192, new ItemOnObjectMessageDecoder()); @@ -201,6 +203,7 @@ public final class Release317 extends Release { register(FriendServerStatusMessage.class, new FriendServerStatusMessageEncoder()); register(IgnoreListMessage.class, new IgnoreListMessageEncoder()); register(SendFriendMessage.class, new SendFriendMessageEncoder()); + register(HintIconMessage.class, new HintIconMessageEncoder()); } } \ No newline at end of file diff --git a/src/org/apollo/net/release/r377/HintIconMessageEncoder.java b/src/org/apollo/net/release/r377/HintIconMessageEncoder.java new file mode 100644 index 00000000..d4e39ff6 --- /dev/null +++ b/src/org/apollo/net/release/r377/HintIconMessageEncoder.java @@ -0,0 +1,34 @@ +package org.apollo.net.release.r377; + +import org.apollo.game.message.impl.HintIconMessage; +import org.apollo.net.codec.game.DataType; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketBuilder; +import org.apollo.net.release.MessageEncoder; + +/** + * A {@link MessageEncoder} for the {@link HintIconMessage}. + * + * @author Major + */ +public final class HintIconMessageEncoder extends MessageEncoder { + + @Override + public GamePacket encode(HintIconMessage message) { + GamePacketBuilder builder = new GamePacketBuilder(199); + int type = message.getType(); + builder.put(DataType.BYTE, type); + + switch (type) { + case HintIconMessage.NPC_TYPE: + case HintIconMessage.PLAYER_TYPE: + builder.put(DataType.SHORT, message.getIndex().get()); + break; + default: + throw new IllegalStateException("Unsupported hint icon type " + type + "."); + } + + return builder.toGamePacket(); + } + +} \ No newline at end of file diff --git a/src/org/apollo/net/release/r377/Release377.java b/src/org/apollo/net/release/r377/Release377.java index 9b368738..ee731707 100644 --- a/src/org/apollo/net/release/r377/Release377.java +++ b/src/org/apollo/net/release/r377/Release377.java @@ -9,6 +9,7 @@ import org.apollo.game.message.impl.DisplayTabInterfaceMessage; import org.apollo.game.message.impl.EnterAmountMessage; import org.apollo.game.message.impl.ForwardPrivateChatMessage; import org.apollo.game.message.impl.FriendServerStatusMessage; +import org.apollo.game.message.impl.HintIconMessage; import org.apollo.game.message.impl.IdAssignmentMessage; import org.apollo.game.message.impl.IgnoreListMessage; import org.apollo.game.message.impl.LogoutMessage; @@ -19,8 +20,8 @@ import org.apollo.game.message.impl.OpenInterfaceSidebarMessage; import org.apollo.game.message.impl.PlayerSynchronizationMessage; import org.apollo.game.message.impl.PositionMessage; import org.apollo.game.message.impl.PrivacyOptionMessage; -import org.apollo.game.message.impl.SectorChangeMessage; import org.apollo.game.message.impl.RemoveTileItemMessage; +import org.apollo.game.message.impl.SectorChangeMessage; import org.apollo.game.message.impl.SendFriendMessage; import org.apollo.game.message.impl.SendObjectMessage; import org.apollo.game.message.impl.ServerChatMessage; @@ -40,6 +41,7 @@ import org.apollo.game.message.impl.UpdateTileItemMessage; import org.apollo.game.message.impl.UpdateWeightMessage; import org.apollo.net.meta.PacketMetaDataGroup; import org.apollo.net.release.Release; +import org.apollo.net.release.r317.HintIconMessageEncoder; /** * A {@link Release} implementation for the 377 protocol. @@ -128,7 +130,7 @@ public final class Release377 extends Release { register(36, new MagicOnItemMessageDecoder()); register(187, new FocusUpdateMessageDecoder()); - register(19, new MouseClickedMessageDecoder()); + register(19, new MouseClickedMessageDecoder()); register(171, new FlaggedMouseEventMessageDecoder()); register(140, new ArrowKeyMessageDecoder()); register(176, new PrivacyOptionMessageDecoder()); @@ -196,6 +198,7 @@ public final class Release377 extends Release { register(FriendServerStatusMessage.class, new FriendServerStatusMessageEncoder()); register(IgnoreListMessage.class, new IgnoreListMessageEncoder()); register(SendFriendMessage.class, new SendFriendMessageEncoder()); + register(HintIconMessage.class, new HintIconMessageEncoder()); } } \ No newline at end of file From 496de2b7f8bd91d0543c570de20d4ad51c528126 Mon Sep 17 00:00:00 2001 From: Major- Date: Wed, 25 Feb 2015 16:33:20 +0000 Subject: [PATCH 2/9] Update wilderness coordinates to the ones used by Jagex. --- data/plugins/areas/actions.rb | 2 +- data/plugins/areas/areas.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/plugins/areas/actions.rb b/data/plugins/areas/actions.rb index 316c63e6..e72f42f4 100644 --- a/data/plugins/areas/actions.rb +++ b/data/plugins/areas/actions.rb @@ -49,7 +49,7 @@ end # Defines the pvp area action. area_action :pvp do on_entry { |player| player.in_pvp = true } - on_exit { |player| player.in_pvp = true } + on_exit { |player| player.in_pvp = false } end # Defines the wilderness area action. diff --git a/data/plugins/areas/areas.rb b/data/plugins/areas/areas.rb index 3dc06e8f..cb540224 100644 --- a/data/plugins/areas/areas.rb +++ b/data/plugins/areas/areas.rb @@ -42,5 +42,5 @@ def area(hash) end # Coordinates refer to the bottom-left position (min_x, min_y) and the top-right position (max_x, max_y), followed by the height (optional). -area :name => :wilderness, :coordinates => [ 2944, 3520, 3391, 3967, 0 ], :actions => [ :pvp, :multicombat, :wilderness ] +area :name => :wilderness, :coordinates => [ 2944, 3520, 3392, 6400, 0 ], :actions => [ :pvp, :multicombat, :wilderness ] area :name => :duel_arena, :coordinates => [ 3327, 3200, 3392, 3286 ], :actions => :pvp \ No newline at end of file From 5814c5b44f7249f80f398ad0cd79613e9e333ad6 Mon Sep 17 00:00:00 2001 From: Major- Date: Wed, 25 Feb 2015 16:33:57 +0000 Subject: [PATCH 3/9] Correct typo in name-lookup plugin. --- data/plugins/util/name-lookup.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/plugins/util/name-lookup.rb b/data/plugins/util/name-lookup.rb index f45b5788..febb8be8 100644 --- a/data/plugins/util/name-lookup.rb +++ b/data/plugins/util/name-lookup.rb @@ -24,7 +24,7 @@ def lookup_entity(type, name) return cached unless cached.nil? id = name[name.rindex(' ') + 1, name.length - 1].to_i if name.include?(' ') - id = find_entities(type, name, 1).first if (id .nil? || id.zero?) + id = find_entities(type, name, 1).first if (id.nil? || id.zero?) raise "The #{type} called #{name} could not be identified." if id.nil? From 649dcfc2394428ef87c6f400ef84e816e62238e0 Mon Sep 17 00:00:00 2001 From: Major- Date: Wed, 25 Feb 2015 16:35:10 +0000 Subject: [PATCH 4/9] Parse release number properly, use Preconditions in IndexedFileSystem. --- src/org/apollo/Server.java | 2 +- src/org/apollo/fs/IndexedFileSystem.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/org/apollo/Server.java b/src/org/apollo/Server.java index 2534bd29..dc97737c 100644 --- a/src/org/apollo/Server.java +++ b/src/org/apollo/Server.java @@ -167,7 +167,7 @@ public final class Server { serviceManager.startAll(); int releaseNo = context.getRelease().getReleaseNumber(); - IndexedFileSystem fs = new IndexedFileSystem(Paths.get("data/fs/", Integer.toString(releaseNo)), true); + IndexedFileSystem fs = new IndexedFileSystem(Paths.get("data/fs", Integer.toString(releaseNo)), true); World.getWorld().init(releaseNo, fs, manager); } diff --git a/src/org/apollo/fs/IndexedFileSystem.java b/src/org/apollo/fs/IndexedFileSystem.java index 40b3bef1..50d3ee0c 100644 --- a/src/org/apollo/fs/IndexedFileSystem.java +++ b/src/org/apollo/fs/IndexedFileSystem.java @@ -177,7 +177,7 @@ public final class IndexedFileSystem implements Closeable { int nextType = header[7] & 0xFF; if (i != curChunk) { - throw new IOException("Chunk id mismatch."); + Preconditions.checkArgument(i == curChunk, "Chunk id mismatch."); } int chunkSize = size - read; From a0895696064342887c8f9806a415be20ee2bdcec Mon Sep 17 00:00:00 2001 From: Major- Date: Wed, 25 Feb 2015 16:36:02 +0000 Subject: [PATCH 5/9] Use Optional in InterfaceSet. --- .../apollo/game/model/inter/InterfaceSet.java | 63 ++++++++++--------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/src/org/apollo/game/model/inter/InterfaceSet.java b/src/org/apollo/game/model/inter/InterfaceSet.java index 63fd4ea2..72835dac 100644 --- a/src/org/apollo/game/model/inter/InterfaceSet.java +++ b/src/org/apollo/game/model/inter/InterfaceSet.java @@ -2,6 +2,7 @@ package org.apollo.game.model.inter; import java.util.HashMap; import java.util.Map; +import java.util.Optional; import org.apollo.game.message.impl.CloseInterfaceMessage; import org.apollo.game.message.impl.EnterAmountMessage; @@ -34,12 +35,12 @@ public final class InterfaceSet { /** * The current enter amount listener. */ - private EnterAmountListener amountListener; + private Optional amountListener = Optional.empty(); /** * The current chat box dialogue listener. */ - private DialogueListener dialogueListener; + private Optional dialogueListener = Optional.empty(); /** * A map of open interfaces. @@ -49,12 +50,12 @@ public final class InterfaceSet { /** * The current listener. */ - private InterfaceListener listener; + private Optional listener = Optional.empty(); /** * The player whose interfaces are being managed. */ - private final Player player; // TODO: maybe switch to a listener system like the inventory? + private final Player player; /** * Creates an interface set. @@ -72,8 +73,8 @@ public final class InterfaceSet { * @return {@code true} if the message handler chain should be broken. */ public boolean buttonClicked(int button) { - if (dialogueListener != null) { - return dialogueListener.buttonClicked(button); + if (dialogueListener.isPresent()) { + return dialogueListener.get().buttonClicked(button); } return false; } @@ -86,21 +87,6 @@ public final class InterfaceSet { player.send(new CloseInterfaceMessage()); } - /** - * An internal method for closing the interface, notifying the listener if appropriate, but not sending any - * messages. - */ - private void closeAndNotify() { - amountListener = null; - dialogueListener = null; - - interfaces.clear(); - if (listener != null) { - listener.interfaceClosed(); - listener = null; - } - } - /** * Checks if this interface sets contains the specified interface. * @@ -125,8 +111,8 @@ public final class InterfaceSet { * Called when the player has clicked the "Click here to continue" button on a dialogue. */ public void continueRequested() { - if (dialogueListener != null) { - dialogueListener.continued(); + if (dialogueListener.isPresent()) { + dialogueListener.get().continued(); } } @@ -136,9 +122,9 @@ public final class InterfaceSet { * @param amount The amount. */ public void enteredAmount(int amount) { - if (amountListener != null) { - amountListener.amountEntered(amount); - amountListener = null; + if (amountListener.isPresent()) { + amountListener.get().amountEntered(amount); + amountListener = Optional.empty(); } } @@ -158,8 +144,8 @@ public final class InterfaceSet { public void openDialogue(DialogueListener listener, int dialogueId) { closeAndNotify(); - this.dialogueListener = listener; - this.listener = listener; + this.dialogueListener = Optional.ofNullable(listener); + this.listener = Optional.ofNullable(listener); interfaces.put(InterfaceType.DIALOGUE, dialogueId); player.send(new OpenDialogueInterfaceMessage(dialogueId)); @@ -180,7 +166,7 @@ public final class InterfaceSet { * @param listener The enter amount listener. */ public void openEnterAmountDialogue(EnterAmountListener listener) { - amountListener = listener; + amountListener = Optional.of(listener); player.send(new EnterAmountMessage()); } @@ -201,7 +187,7 @@ public final class InterfaceSet { */ public void openWindow(InterfaceListener listener, int windowId) { closeAndNotify(); - this.listener = listener; + this.listener = Optional.ofNullable(listener); interfaces.put(InterfaceType.WINDOW, windowId); player.send(new OpenInterfaceMessage(windowId)); @@ -226,7 +212,7 @@ public final class InterfaceSet { */ public void openWindowWithSidebar(InterfaceListener listener, int windowId, int sidebarId) { closeAndNotify(); - this.listener = listener; + this.listener = Optional.ofNullable(listener); interfaces.put(InterfaceType.WINDOW, windowId); interfaces.put(InterfaceType.SIDEBAR, sidebarId); @@ -243,4 +229,19 @@ public final class InterfaceSet { return interfaces.size(); } + /** + * An internal method for closing the interface, notifying the listener if appropriate, but not sending any + * messages. + */ + private void closeAndNotify() { + amountListener = Optional.empty(); + dialogueListener = Optional.empty(); + + interfaces.clear(); + if (listener.isPresent()) { + listener.get().interfaceClosed(); + listener = Optional.empty(); + } + } + } \ No newline at end of file From 341ea1e422758c0f51b3b1bb808e48c0e0716e83 Mon Sep 17 00:00:00 2001 From: Major- Date: Wed, 25 Feb 2015 16:36:41 +0000 Subject: [PATCH 6/9] Rename 'p' to 'other' in PlayerSynchronizationTask. --- .../sync/task/PlayerSynchronizationTask.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/org/apollo/game/sync/task/PlayerSynchronizationTask.java b/src/org/apollo/game/sync/task/PlayerSynchronizationTask.java index 17dae8b4..e3155924 100644 --- a/src/org/apollo/game/sync/task/PlayerSynchronizationTask.java +++ b/src/org/apollo/game/sync/task/PlayerSynchronizationTask.java @@ -69,13 +69,13 @@ public final class PlayerSynchronizationTask extends SynchronizationTask { List segments = new ArrayList<>(); for (Iterator it = localPlayers.iterator(); it.hasNext();) { - Player p = it.next(); - if (!p.isActive() || p.isTeleporting() - || p.getPosition().getLongestDelta(player.getPosition()) > player.getViewingDistance()) { + Player other = it.next(); + if (!other.isActive() || other.isTeleporting() + || other.getPosition().getLongestDelta(player.getPosition()) > player.getViewingDistance()) { it.remove(); segments.add(new RemoveMobSegment()); } else { - segments.add(new MovementSegment(p.getBlockSet(), p.getDirections())); + segments.add(new MovementSegment(other.getBlockSet(), other.getDirections())); } } @@ -83,7 +83,7 @@ public final class PlayerSynchronizationTask extends SynchronizationTask { MobRepository repository = World.getWorld().getPlayerRepository(); for (Iterator it = repository.iterator(); it.hasNext();) { - Player p = it.next(); + Player other = it.next(); if (localPlayers.size() >= 255) { player.flagExcessivePlayers(); break; @@ -91,19 +91,19 @@ public final class PlayerSynchronizationTask extends SynchronizationTask { break; } - if (p != player && p.getPosition().isWithinDistance(player.getPosition(), player.getViewingDistance()) - && !localPlayers.contains(p)) { - localPlayers.add(p); + if (other != player && other.getPosition().isWithinDistance(player.getPosition(), player.getViewingDistance()) + && !localPlayers.contains(other)) { + localPlayers.add(other); added++; - blockSet = p.getBlockSet(); + blockSet = other.getBlockSet(); if (!blockSet.contains(AppearanceBlock.class)) { // TODO check if client has cached appearance blockSet = blockSet.clone(); - blockSet.add(SynchronizationBlock.createAppearanceBlock(p)); + blockSet.add(SynchronizationBlock.createAppearanceBlock(other)); } - segments.add(new AddPlayerSegment(blockSet, p.getIndex(), p.getPosition())); + segments.add(new AddPlayerSegment(blockSet, other.getIndex(), other.getPosition())); } } From 612ed89ba7f1c5bc424629de2a4a53026be103dd Mon Sep 17 00:00:00 2001 From: Major- Date: Wed, 25 Feb 2015 16:37:34 +0000 Subject: [PATCH 7/9] Add 'MOVE' operation type to Sector, make minor code improvements to Mob and Npc. --- src/org/apollo/game/model/area/Sector.java | 78 +++++++++++++------ .../game/model/area/SectorOperation.java | 5 ++ src/org/apollo/game/model/entity/Mob.java | 17 ++-- src/org/apollo/game/model/entity/Npc.java | 24 +++--- 4 files changed, 81 insertions(+), 43 deletions(-) diff --git a/src/org/apollo/game/model/area/Sector.java b/src/org/apollo/game/model/area/Sector.java index a8c140fc..e1c35d46 100644 --- a/src/org/apollo/game/model/area/Sector.java +++ b/src/org/apollo/game/model/area/Sector.java @@ -13,7 +13,6 @@ import org.apollo.game.model.entity.Entity; import org.apollo.game.model.entity.Entity.EntityType; import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; /** @@ -23,16 +22,16 @@ import com.google.common.collect.ImmutableSet; */ public final class Sector { - /** - * The default size of newly-created sets, to reduce memory usage. - */ - private static final int DEFAULT_SET_SIZE = 2; - /** * The width and length of a sector, in tiles. */ public static final int SECTOR_SIZE = 8; + /** + * The default size of newly-created sets, to reduce memory usage. + */ + private static final int DEFAULT_SET_SIZE = 2; + /** * The sector coordinates of this sector. */ @@ -72,6 +71,7 @@ public final class Sector { * register it to 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(); @@ -82,17 +82,6 @@ public final class Sector { notifyListeners(entity, SectorOperation.ADD); } - /** - * Checks that the specified {@link Position} is included in this sector. - * - * @param position The position. - * @throws IllegalArgumentException If the specified position is not included in this sector. - */ - private void checkPosition(Position position) { - Preconditions.checkArgument(coordinates.equals(SectorCoordinates.fromPosition(position)), - "Position is not included in this sector."); - } - /** * Checks if this sector contains the specified entity. *

@@ -118,24 +107,24 @@ public final class Sector { } /** - * Gets a shallow copy of the {@link List} of {@link Entity} objects at the specified {@link Position}. The returned - * type will be {@link ImmutableList}. + * 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 List getEntities(Position position) { - return ImmutableList.copyOf(entities.computeIfAbsent(position, key -> new HashSet<>(DEFAULT_SET_SIZE))); + public Set getEntities(Position position) { + return ImmutableSet.copyOf(entities.computeIfAbsent(position, key -> new HashSet<>(DEFAULT_SET_SIZE))); } /** - * Gets a shallow copy of the {@link List} of {@link Entity}s with the specified {@link EntityType}. The returned - * list will be an {@link ImmutableList}. Type will be inferred from the call, so ensure that the entity type and - * the reference correspond, or this method will fail at runtime. + * 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 + * correspond, or this method will fail at runtime. * * @param position The {@link Position} containing the entities. * @param type The {@link EntityType}. - * @return The list of entities. + * @return The set of entities. */ public Set getEntities(Position position, EntityType type) { Set local = entities.computeIfAbsent(position, key -> new HashSet<>(DEFAULT_SET_SIZE)); @@ -145,6 +134,34 @@ public final class Sector { return ImmutableSet.copyOf(filtered); } + /** + * Moves the {@link Entity} that was in the specified {@code old} {@link Position}, to the current position of the + * entity. + *

+ * 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. + * @throws IllegalArgumentException If either of the positions do not belong to this sector. + */ + public void moveEntity(Position old, Entity entity) { + Position position = entity.getPosition(); + checkPosition(old); + checkPosition(position); + + Set local = entities.get(old); + + if (local == null || !local.remove(entity)) { + throw new IllegalArgumentException("Entity belongs in this sector but does not exist."); + } + + local = entities.computeIfAbsent(position, key -> new HashSet<>(DEFAULT_SET_SIZE)); + + local.add(entity); + notifyListeners(entity, SectorOperation.MOVE); + + } + /** * Notifies the {@link SectorListener}s registered to this sector that an update has occurred. * @@ -174,4 +191,15 @@ public final class Sector { notifyListeners(entity, SectorOperation.REMOVE); } + /** + * Checks that the specified {@link Position} is included in this sector. + * + * @param position The position. + * @throws IllegalArgumentException If the specified position is not included in this sector. + */ + private void checkPosition(Position position) { + Preconditions.checkArgument(coordinates.equals(SectorCoordinates.fromPosition(position)), + "Position is not included in this sector."); + } + } \ No newline at end of file diff --git a/src/org/apollo/game/model/area/SectorOperation.java b/src/org/apollo/game/model/area/SectorOperation.java index 14997350..a0df172d 100644 --- a/src/org/apollo/game/model/area/SectorOperation.java +++ b/src/org/apollo/game/model/area/SectorOperation.java @@ -12,6 +12,11 @@ public enum SectorOperation { */ ADD, + /** + * The move operation, when an entity has moved positions, but is still in the same sector. + */ + MOVE, + /** * The remove operation, when an entity has been removed from a sector. */ diff --git a/src/org/apollo/game/model/entity/Mob.java b/src/org/apollo/game/model/entity/Mob.java index ad4c6512..9240dc8e 100644 --- a/src/org/apollo/game/model/entity/Mob.java +++ b/src/org/apollo/game/model/entity/Mob.java @@ -419,15 +419,20 @@ public abstract class Mob extends Entity { * @param position The position. */ public final void setPosition(Position position) { + Position old = this.position; SectorRepository repository = World.getWorld().getSectorRepository(); - Sector current = repository.fromPosition(this.position); + Sector current = repository.fromPosition(old); - Sector next = position.inside(current) ? current : repository.fromPosition(position); + if (position.inside(current)) { + this.position = position; + current.moveEntity(old, this); + } else { + Sector next = repository.fromPosition(position); + current.removeEntity(this); + this.position = position; // addEntity relies on the position being updated, so do that first. - current.removeEntity(this); - this.position = position; // addEntity relies on the position being updated, so do that first. - - next.addEntity(this); + next.addEntity(this); + } } /** diff --git a/src/org/apollo/game/model/entity/Npc.java b/src/org/apollo/game/model/entity/Npc.java index 478b77a1..66532a96 100644 --- a/src/org/apollo/game/model/entity/Npc.java +++ b/src/org/apollo/game/model/entity/Npc.java @@ -13,19 +13,19 @@ import com.google.common.base.MoreObjects; import com.google.common.base.Preconditions; /** - * An {@link Npc} is a {@link Mob} that is not being controlled by a player. + * A {@link Mob} that is not controlled by a player. * * @author Major */ public final class Npc extends Mob { /** - * The positions representing the bounds (i.e. walking limits) of this npc. + * The positions representing the bounds (i.e. walking limits) of this Npc. */ private Position[] boundary; /** - * Creates a new npc with the specified id and {@link Position}. + * Creates a new Npc with the specified id and {@link Position}. * * @param id The id. * @param position The position. @@ -35,7 +35,7 @@ public final class Npc extends Mob { } /** - * Creates a new npc with the specified {@link NpcDefinition} and {@link Position}. + * Creates a new Npc with the specified {@link NpcDefinition} and {@link Position}. * * @param position The position. * @param definition The definition. @@ -57,7 +57,7 @@ public final class Npc extends Mob { } /** - * Gets the boundary of this npc. + * Gets the boundary of this Npc. * * @return The boundary. */ @@ -71,7 +71,7 @@ public final class Npc extends Mob { } /** - * Gets the id of this npc. + * Gets the id of this Npc. * * @return The id. */ @@ -87,16 +87,16 @@ public final class Npc extends Mob { } /** - * Indicates whether or not this npc is bound to a specific set of coordinates. + * Indicates whether or not this Npc is bound to a specific set of coordinates. * - * @return {@code true} if the npc is bound, otherwise {@code false}. + * @return {@code true} if the Npc is bound, otherwise {@code false}. */ public boolean isBound() { return boundary == null; } /** - * Sets the boundary of this npc. + * Sets the boundary of this Npc. * * @param boundary The boundary. */ @@ -111,7 +111,7 @@ public final class Npc extends Mob { } /** - * Transforms this npc into the npc with the specified id. + * Transforms this Npc into the Npc with the specified id. * * @param id The id. */ @@ -123,10 +123,10 @@ public final class Npc extends Mob { } /** - * Initialises this npc. + * Initialises this Npc. */ private void init() { - // This has to be here instead of in Mob#init because of ordering issues - the player cannot be added to the + // This has to be here instead of in Mob#init because of ordering issues - the Npc cannot be added to the // sector until their credentials have been set, which is only done after the super constructors are called. Sector sector = World.getWorld().getSectorRepository().get(position.getSectorCoordinates()); sector.addEntity(this); From 9e5f454aa5aad163705c50e720db82d823d48f33 Mon Sep 17 00:00:00 2001 From: Major- Date: Wed, 25 Feb 2015 16:38:19 +0000 Subject: [PATCH 8/9] Add dialogue support. --- data/plugins/dialogue/dialogue.rb | 426 ++++++++++++++++++++++++++++++ data/plugins/dialogue/plugin.xml | 16 ++ 2 files changed, 442 insertions(+) create mode 100644 data/plugins/dialogue/dialogue.rb create mode 100644 data/plugins/dialogue/plugin.xml diff --git a/data/plugins/dialogue/dialogue.rb b/data/plugins/dialogue/dialogue.rb new file mode 100644 index 00000000..301cf771 --- /dev/null +++ b/data/plugins/dialogue/dialogue.rb @@ -0,0 +1,426 @@ +require 'java' + +java_import 'org.apollo.game.model.inter.dialogue.DialogueAdapter' +java_import 'org.apollo.game.message.impl.CloseInterfaceMessage' +java_import 'org.apollo.game.message.impl.SetWidgetItemModelMessage' +java_import 'org.apollo.game.message.impl.SetWidgetNpcModelMessage' +java_import 'org.apollo.game.message.impl.SetWidgetPlayerModelMessage' +java_import 'org.apollo.game.message.impl.SetWidgetTextMessage' + +# Defines a dialogue, with the specified name and block. +def dialogue(name, &block) + raise 'Dialogues must have a name and block.' if (name.nil? || block.nil?) + + dialogue = Dialogue.new(name) + dialogue.instance_eval(&block) + dialogue.wrap + DIALOGUES[name] = dialogue +end + +# Defines an opening (i.e. conversation starter) dialogue, which hooks into the chain. +# Allows for a lambda prerequisite to be passed, which takes one argument the player; if the prerequisite evaluates to false, the dialogue will not be opened. +def opening_dialogue(name, prerequisite=nil, &block) + dialogue = dialogue(name, &block) + npc = dialogue.npc + raise 'Npc cannot be null when opening a dialogue.' if npc.nil? + + on :message, :first_npc_action, npc do |ctx, player, event| + player.open_dialogue(name) if (prerequisite.nil? || prerequisite.call(player)) + end +end + +# Declares an emote, with the specified name and id. +def declare_emote(name, id) + EMOTES[name] = id +end + + + +private + +# The hash of dialogue names to dialogues. +DIALOGUES = {} + +# The hash of emote names to ids. +EMOTES = {} + +# The maximum amount of lines of text that can be displayed on a dialogue. +MAXIMUM_LINE_COUNT = 4 + +# The maximum amount of options that can be displayed on a dialogue. +MAXIMUM_OPTION_COUNT = 5 + +# The maximum width of a line, in characters. +MAXIMUM_LINE_WIDTH = 55 + +# The possible types of a dialogue. +DIALOGUE_TYPES = [ :message_with_item, :message_with_model, :npc_speech, :options, :player_speech, :text ] + +# A type of dialogue. +class Dialogue + attr_reader :emote, :name, :media, :options, :text, :title, :type + + # Initializes the Dialogue. + def initialize(name) + @name = name.to_s + @text = [] + @options = [] + end + + # Closes the dialogue interface when the player clicks the 'Click here to continue...' text. + def close + continue(:close => true) + end + + # Defines the event that occurs when a player clicks the 'Click here to continue...' text. + def continue(type=nil, &block) + raise 'Cannot add a continue event on a dialogue with options.' unless @options.size.zero? + raise 'Must declare either a type or a block for a continue event.' if (type.nil? && block.nil?) + + @options << (block.nil? ? get_next_dialogue(type) : block) + end + + # Sets the emote performed by the dialogue head. + def emote(emote=nil) + raise 'Can only perform an emote on :player_speech or :npc_speech dialogues.' unless [ :npc_speech, :player_speech ].include?(@type) + @emote = EMOTES[emote] if emote.kind_of?(Symbol) + @emote + end + + # Gets the media of this dialogue. + def media() + case @type + when :message_with_item then @item + when :npc_speech then @npc + when :message_with_model then @model + else raise "Cannot get media for #{@type}." + end + end + + # Sets the id of the item displayed. + def item(item=nil, scale=nil) + unless item.nil? + raise 'Can only display an item on :message_with_item dialogues.' unless @type == :message_with_item + @item = item + @item_scale = scale + end + + @item + end + + # Sets the id of the model displayed. + def model(model=nil) + unless model.nil? + raise 'Can only display a model on :message_with_model dialogues.' unless @type == :message_with_model + @model = model + end + + @model + end + + # Sets the id of the npc displayed. + def npc(npc=nil) + raise 'Can only display an npc on :npc_speech dialogues.' unless @type == :npc_speech + @npc = lookup_npc(npc) unless npc.nil? + @npc + end + + # Defines an option, displaying the specified message. + def option(message, type) + raise 'Can only display options on an :options dialogue.' unless @type == :options + raise "Cannot display more than #{MAXIMUM_OPTION_COUNT} options on a dialogue." unless @options.size < MAXIMUM_OPTION_COUNT + + @options[text.size] = get_next_dialogue(type) + @text << message + end + + # Gets the array of options. + def options + @options.dup + end + + # Appends a message to the text list. + def text(*message) + unless message.nil? + @text.concat(message) + end + + @text + end + + # Sets the title of the dialogue. + def title(title=nil) + @title = title unless title.nil? + @title + end + + # Sets the type of dialogue. + def type(type=nil) + unless type.nil? + verify_dialogue_type(type) + @type = type + end + + @type + end + + # Wraps text in this Dialogue, inserting extra Dialogues in the chain if necessary. + def wrap + lines = [] + next if @type == :options + + text = @text[0] + segments = []# text.chars.each_slice(MAXIMUM_LINE_WIDTH).map(&:join) # Split text into array of strings with length <= 60. + previous = 0; index = MAXIMUM_LINE_WIDTH + + while index < text.length + index -= 1 until text[index] == ' ' + segments << text[previous..index] + previous = index + index += MAXIMUM_LINE_WIDTH + end + segments << text[previous..text.length] + + if (segments.size <= MAXIMUM_LINE_COUNT) + lines.concat(segments) + @text = @text.drop(1) + insert_copy(@text) if @text.size > 0 + else + remaining = MAXIMUM_LINE_COUNT - segments.size + lines.concat(segments.first(remaining)) + insert_copy(segments.drop(remaining).join().concat(@text.drop(1))) + end + + @text = lines + end + + + # Copies the value of every variable from the specified Dialogue, optionally updating the text array. + def copy_from(dialogue, text=nil) + @emote = dialogue.emote + @item = dialogue.item + @model = dialogue.model + @npc = dialogue.npc + @options = dialogue.options + @text = if text.nil? then dialogue.text.dup else text.dup end + @type = dialogue.type + end + + private + + # Inserts a copy of this Dialogue into the chain, but with different text. + def insert_copy(text) + name = @name + index = name.index('-auto-inserted-') + + id = if index.nil? then 0 else name[name.rindex('-')..-1].to_i + 1 end + index ||= -1 + name = "#{name[0..index]}-auto-inserted-#{id}" + + dialogue = Dialogue.new(name) + dialogue.copy_from(self, text.dup) + dialogue.wrap() + + DIALOGUES[name] = dialogue + @options[0] = ->(player) { send_dialogue(player, dialogue) } + end + + # Decodes the next dialogue interface from the hash, returning a proc. + def get_next_dialogue(hash) + hash.keys.each do |key| + case key + when :close + return ->(player) { player.send(CloseInterfaceMessage.new) } + when :dialogue + return ->(player) { send_dialogue(player, lookup_dialogue(hash[key])) } + else raise "Unrecognised dialogue continue type #{key}." + end + end + end + +end + +# The existing Player class. +class Player + + # Opens the dialogue with the specified name. + def open_dialogue(name) + dialogue = lookup_dialogue(name) + send_dialogue(self, dialogue) + end + +end + + + +# Gets a Dialogue using the name it was registered with. +def lookup_dialogue(name) + dialogue = DIALOGUES[name] + raise "No dialogue named #{name.to_s}." if dialogue.nil? + + dialogue +end + +# Sends the specified dialogue. +def send_dialogue(player, dialogue) + type = dialogue.type + + case type + when :message_with_item then send_item_dialogue(player, dialogue) + when :message_with_model then send_model_dialogue(player, dialogue) + when :npc_speech then send_npc_dialogue(player, dialogue) + when :options then send_options_dialogue(player, dialogue) + when :player_speech then send_player_dialogue(player, dialogue) + when :text then send_text_dialogue(player, dialogue) + else raise "Unrecognised dialogue type #{type}." + end +end + +# The dialogue interface ids for dialogues that only display text, ordered by line count. +TEXT_DIALOGUE_IDS = [ 356, 359, 363, 368, 374 ] + +# The dialogue interface ids for dialogues that display the head of the player, ordered by line count. +PLAYER_DIALOGUE_IDS = [ 968, 973, 979, 986 ] + +# The dialogue interface ids for dialogues that display the head of an npc, ordered by line count. +NPC_DIALOGUE_IDS = [ 4882, 4887, 4893, 4900 ] + +# The dialogue interface ids for option dialogues, ordered by (option_count - 1) +OPTIONS_DIALOGUE_IDS = [ 2459, 2469, 2480, 2492 ] + +# Sends a dialogue displaying only text. +def send_text_dialogue(player, dialogue) + title = dialogue.title + send_generic_dialogue(player, dialogue, title, TEXT_DIALOGUE_IDS) +end + +# Sends a dialogue displaying the player's head. +def send_player_dialogue(player, dialogue) + send_generic_dialogue(player, dialogue, PLAYERS_DIALOGUE_IDS, ->(id) { SetWidgetPlayerModelMessage.new(id + 1) }) +end + +# Sends a dialogue displaying the head of an npc. +def send_npc_dialogue(player, dialogue) + npc = dialogue.npc + name = NpcDefinition.lookup(npc).name.to_s + name = "" if (name.nil? || name == "null") + + send_generic_dialogue(player, dialogue, name, NPC_DIALOGUE_IDS, ->(id) { SetWidgetNpcModelMessage.new(id + 1, npc)}) +end + + +# Sends a dialogue displaying an event. +def send_generic_dialogue(player, dialogue, title, ids, event=nil) + text = dialogue.text + dialogue_id = ids[text.size - 1] + player.send(event.call(dialogue_id)) unless event.nil? + + set_text(player, dialogue_title_id(dialogue_id), title) + + text.each_index { |index| set_text(player, dialogue_text_id(dialogue_id, index), text[index]) } + player.interface_set.open_dialogue(ContinueDialogueAdapter.new(player, dialogue.options[0]), dialogue_id) # TODO listener!!! +end + + +# Sends an options dialogue interface. +def send_options_dialogue(player, dialogue) + options = dialogue.options + size = options.size + raise 'Illegal options count: must be between 2 and 5, inclusive.' unless (2..5).include?(size) + + text = dialogue.text + dialogue_id = OPTIONS_DIALOGUE_IDS[size - 1] + + question = dialogue.title + set_text(player, dialogue_question_id(dialogue_id), question) + + text.each_index { |index| set_text(player, dialogue_option_id(dialogue_id, index), text[index]) } + player.interface_set.open_dialogue(OptionDialogueAdapter.new(player, options), dialogue_id) # TODO listener!!! +end + + +# A DialogueAdapter for dialogues with a 'Click here to continue...' message. +class ContinueDialogueAdapter < DialogueAdapter + + # Creates the ContinueDialogueAdadpter. + def initialize(player, continue) + super() + @player = player + @continue = continue + end + + # Executes the 'continue' lambda when the player clicks the 'Click here to continue...' message. + def continued() + @continue.call(@player) + end + +end + + +# A DialogueAdapter for dialogues with a set of options that can be selected. +class OptionDialogueAdapter < DialogueAdapter + + # Creates the OptionDialogueAdadpter. + def initialize(player, options) + super() + @player = player + @options = options.dup + end + + # Executes an option. + def button_clicked(button) + option = OPTIONS_DIALOGUE_IDS.find_index(button) + options[option].call(@player) + end + +end + + +# Gets the widget id of the question, for an options dialogue interface. +def dialogue_question_id(id) + id + 1 +end + +# Gets the widget id of a dialogue option. +def dialogue_option_id(id, option) + id + 1 + option +end + +# Gets the widget id of a dialogue text line. +def dialogue_text_id(id, line) + id + 3 + line +end + +# Gets the widget id of a dialogue title. +def dialogue_title_id(id) + id + 2 +end + +# Sets the text of a widget. +def set_text(player, id, message) + player.send(SetWidgetTextMessage.new(id, message)) +end + +# Verifies that the dialogue type exists. +def verify_dialogue_type(type) + raise "Unrecognised dialogue type #{type}, expected one of #{DIALOGUE_TYPES}." unless DIALOGUE_TYPES.include?(type) +end + +# The spacing of each character glyph, for the font used for dialogue. TODO decode the font from the cache. +GLYPH_SPACING = [ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 7, 14, 9, 12, 12, 4, 5, + 5, 10, 8, 4, 8, 4, 7, 9, 7, 9, 8, 8, 8, 9, 7, 9, 9, 4, 5, 7, + 9, 7, 9, 14, 9, 8, 8, 8, 7, 7, 9, 8, 6, 8, 8, 7, 10, 9, 9, 8, + 9, 8, 8, 6, 9, 8, 10, 8, 8, 8, 6, 7, 6, 9, 10, 5, 8, 8, 7, 8, + 8, 7, 8, 8, 4, 7, 7, 4, 10, 8, 8, 8, 8, 6, 8, 6, 8, 8, 9, 8, + 8, 8, 6, 4, 6, 12, 3, 10, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 4, 8, 11, 8, 8, 4, 8, 7, 12, 6, 7, 9, 5, 12, 5, 6, 10, 6, 6, 6, + 8, 8, 4, 5, 5, 6, 7, 11, 11, 11, 9, 9, 9, 9, 9, 9, 9, 13, 8, 8, + 8, 8, 8, 4, 4, 5, 4, 8, 9, 9, 9, 9, 9, 9, 8, 10, 9, 9, 9, 9, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 13, 6, 8, 8, 8, 8, 4, 4, 5, 4, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 ] + +def get_width(char) + +end \ No newline at end of file diff --git a/data/plugins/dialogue/plugin.xml b/data/plugins/dialogue/plugin.xml new file mode 100644 index 00000000..667266b9 --- /dev/null +++ b/data/plugins/dialogue/plugin.xml @@ -0,0 +1,16 @@ + + + dialogue + 0.1 + Dialogue + Adds dialogue support. + + Major + + + + + + util + + \ No newline at end of file From b3befcac51e71f183f4d6c5e6a1f87951218e278 Mon Sep 17 00:00:00 2001 From: Major- Date: Wed, 25 Feb 2015 21:02:41 +0000 Subject: [PATCH 9/9] Remove debug message from HintIconMessageEncoder. --- src/org/apollo/net/release/r317/HintIconMessageEncoder.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/org/apollo/net/release/r317/HintIconMessageEncoder.java b/src/org/apollo/net/release/r317/HintIconMessageEncoder.java index 8df96da4..b8be56c3 100644 --- a/src/org/apollo/net/release/r317/HintIconMessageEncoder.java +++ b/src/org/apollo/net/release/r317/HintIconMessageEncoder.java @@ -28,8 +28,6 @@ public final class HintIconMessageEncoder extends MessageEncoder