From 4abea0cc6ae2bff93955d63d08a387adb57cd8c7 Mon Sep 17 00:00:00 2001 From: Major- Date: Sun, 27 Oct 2013 17:45:36 +0000 Subject: [PATCH] default apollo --- LICENSE | 14 + README | 55 ++ build.xml | 93 ++ data/equipment-317.dat | Bin 0 -> 22122 bytes data/equipment-377.dat | Bin 0 -> 25625 bytes data/events.xml | 73 ++ data/login.xml | 4 + data/note-317.dat | Bin 0 -> 10565 bytes data/note-377.dat | Bin 0 -> 12362 bytes data/plugins/bank/bank.rb | 33 + data/plugins/bank/plugin.xml | 14 + data/plugins/bootstrap.rb | 167 ++++ data/plugins/cmd-bank/bank.rb | 6 + data/plugins/cmd-bank/plugin.xml | 14 + data/plugins/cmd-item/item.rb | 27 + data/plugins/cmd-item/plugin.xml | 14 + data/plugins/cmd-skill/plugin.xml | 14 + data/plugins/cmd-skill/skill.rb | 9 + data/plugins/cmd-teleport/plugin.xml | 14 + data/plugins/cmd-teleport/teleport.rb | 19 + data/plugins/dummy/dummy.rb | 55 ++ data/plugins/dummy/plugin.xml | 14 + data/plugins/hello/hello.rb | 7 + data/plugins/hello/plugin.xml | 14 + data/plugins/logout/logout.rb | 5 + data/plugins/logout/plugin.xml | 14 + data/plugins/mining/gem.rb | 19 + data/plugins/mining/mining.rb | 163 ++++ data/plugins/mining/ore.rb | 96 ++ data/plugins/mining/pickaxe.rb | 31 + data/plugins/mining/plugin.xml | 22 + data/plugins/mining/respawn.rb | 18 + data/services.xml | 5 + data/synchronizer.xml | 3 + etc/jsr166.jar | Bin 0 -> 544543 bytes etc/logo/logo.png | Bin 0 -> 18706 bytes etc/logo/logo.pspimage | Bin 0 -> 27092 bytes etc/logo/logo_trans.png | Bin 0 -> 21266 bytes etc/logo/logo_trans.pspimage | Bin 0 -> 26445 bytes etc/logo/logo_trans_alt.png | Bin 0 -> 14298 bytes etc/logo/logo_trans_alt.pspimage | Bin 0 -> 24762 bytes etc/logo/logo_trans_alt_sm.png | Bin 0 -> 10095 bytes ivy.xml | 14 + ivysettings.xml | 24 + .../burtleburtle/bob/rand/IsaacRandom.java | 298 ++++++ .../burtleburtle/bob/rand/package-info.java | 4 + src/org/apollo/Server.java | 177 ++++ src/org/apollo/ServerContext.java | 79 ++ src/org/apollo/Service.java | 35 + src/org/apollo/ServiceManager.java | 115 +++ src/org/apollo/fs/FileDescriptor.java | 45 + src/org/apollo/fs/FileSystemConstants.java | 46 + src/org/apollo/fs/Index.java | 62 ++ src/org/apollo/fs/IndexedFileSystem.java | 291 ++++++ src/org/apollo/fs/archive/Archive.java | 97 ++ src/org/apollo/fs/archive/ArchiveEntry.java | 47 + src/org/apollo/fs/archive/package-info.java | 4 + src/org/apollo/fs/package-info.java | 5 + .../fs/parser/ItemDefinitionParser.java | 186 ++++ src/org/apollo/fs/parser/package-info.java | 4 + src/org/apollo/game/GameConstants.java | 26 + src/org/apollo/game/GamePulseHandler.java | 39 + src/org/apollo/game/GameService.java | 174 ++++ src/org/apollo/game/action/Action.java | 58 ++ .../apollo/game/action/DistancedAction.java | 77 ++ src/org/apollo/game/action/package-info.java | 5 + src/org/apollo/game/command/Command.java | 45 + .../game/command/CommandDispatcher.java | 49 + .../apollo/game/command/CommandListener.java | 19 + .../game/command/CreditsCommandListener.java | 59 ++ .../command/PrivilegedCommandListener.java | 40 + src/org/apollo/game/command/package-info.java | 4 + src/org/apollo/game/event/Event.java | 9 + .../game/event/handler/EventHandler.java | 21 + .../event/handler/EventHandlerContext.java | 17 + .../handler/chain/EventHandlerChain.java | 67 ++ .../handler/chain/EventHandlerChainGroup.java | 38 + .../event/handler/chain/package-info.java | 4 + .../handler/impl/BankButtonEventHandler.java | 34 + .../event/handler/impl/BankEventHandler.java | 88 ++ .../impl/CharacterDesignEventHandler.java | 22 + .../CharacterDesignVerificationHandler.java | 81 ++ .../event/handler/impl/ChatEventHandler.java | 20 + .../handler/impl/ChatVerificationHandler.java | 23 + .../impl/ClosedInterfaceEventHandler.java | 19 + .../handler/impl/CommandEventHandler.java | 31 + .../impl/EnteredAmountEventHandler.java | 19 + .../event/handler/impl/EquipEventHandler.java | 143 +++ .../handler/impl/RemoveEventHandler.java | 61 ++ .../handler/impl/SwitchItemEventHandler.java | 46 + .../event/handler/impl/WalkEventHandler.java | 40 + .../game/event/handler/impl/package-info.java | 4 + .../game/event/handler/package-info.java | 4 + .../apollo/game/event/impl/ButtonEvent.java | 32 + .../game/event/impl/CharacterDesignEvent.java | 34 + src/org/apollo/game/event/impl/ChatEvent.java | 77 ++ .../game/event/impl/CloseInterfaceEvent.java | 11 + .../game/event/impl/ClosedInterfaceEvent.java | 11 + .../apollo/game/event/impl/CommandEvent.java | 32 + .../game/event/impl/EnterAmountEvent.java | 12 + .../game/event/impl/EnteredAmountEvent.java | 32 + .../apollo/game/event/impl/EquipEvent.java | 62 ++ .../game/event/impl/FifthItemActionEvent.java | 19 + .../game/event/impl/FirstItemActionEvent.java | 19 + .../event/impl/FirstObjectActionEvent.java | 20 + .../event/impl/FourthItemActionEvent.java | 19 + .../game/event/impl/IdAssignmentEvent.java | 48 + .../game/event/impl/ItemActionEvent.java | 77 ++ .../game/event/impl/KeepAliveEvent.java | 32 + .../apollo/game/event/impl/LogoutEvent.java | 11 + .../game/event/impl/ObjectActionEvent.java | 63 ++ .../game/event/impl/OpenInterfaceEvent.java | 32 + .../event/impl/OpenInterfaceSidebarEvent.java | 47 + .../impl/PlayerSynchronizationEvent.java | 111 +++ .../game/event/impl/RegionChangeEvent.java | 33 + .../event/impl/SecondItemActionEvent.java | 19 + .../event/impl/SecondObjectActionEvent.java | 20 + .../game/event/impl/ServerMessageEvent.java | 32 + .../event/impl/SetInterfaceTextEvent.java | 47 + .../game/event/impl/SwitchItemEvent.java | 86 ++ .../event/impl/SwitchTabInterfaceEvent.java | 47 + .../game/event/impl/ThirdItemActionEvent.java | 19 + .../event/impl/ThirdObjectActionEvent.java | 20 + .../game/event/impl/UpdateItemsEvent.java | 48 + .../game/event/impl/UpdateSkillEvent.java | 48 + .../event/impl/UpdateSlottedItemsEvent.java | 48 + src/org/apollo/game/event/impl/WalkEvent.java | 51 + .../apollo/game/event/impl/package-info.java | 4 + src/org/apollo/game/event/package-info.java | 4 + src/org/apollo/game/model/Animation.java | 198 ++++ src/org/apollo/game/model/Appearance.java | 101 ++ src/org/apollo/game/model/Character.java | 366 ++++++++ src/org/apollo/game/model/Direction.java | 124 +++ .../apollo/game/model/EquipmentConstants.java | 71 ++ src/org/apollo/game/model/Gender.java | 40 + src/org/apollo/game/model/Graphic.java | 82 ++ src/org/apollo/game/model/InterfaceSet.java | 176 ++++ src/org/apollo/game/model/InterfaceType.java | 40 + src/org/apollo/game/model/Inventory.java | 553 +++++++++++ .../apollo/game/model/InventoryConstants.java | 31 + src/org/apollo/game/model/Item.java | 72 ++ src/org/apollo/game/model/Player.java | 519 ++++++++++ src/org/apollo/game/model/Position.java | 220 +++++ src/org/apollo/game/model/Skill.java | 181 ++++ src/org/apollo/game/model/SkillSet.java | 316 +++++++ src/org/apollo/game/model/SlottedItem.java | 45 + src/org/apollo/game/model/WalkingQueue.java | 248 +++++ src/org/apollo/game/model/World.java | 227 +++++ src/org/apollo/game/model/WorldConstants.java | 21 + .../game/model/def/EquipmentDefinition.java | 203 ++++ .../apollo/game/model/def/ItemDefinition.java | 363 +++++++ .../model/def/StaticObjectDefinition.java | 11 + .../apollo/game/model/def/package-info.java | 5 + .../game/model/inter/EnterAmountListener.java | 15 + .../game/model/inter/InterfaceListener.java | 14 + .../game/model/inter/bank/BankConstants.java | 36 + .../bank/BankDepositEnterAmountListener.java | 46 + .../inter/bank/BankInterfaceListener.java | 47 + .../game/model/inter/bank/BankUtils.java | 144 +++ .../bank/BankWithdrawEnterAmountListener.java | 46 + .../game/model/inter/bank/package-info.java | 4 + .../apollo/game/model/inter/package-info.java | 4 + .../model/inter/quest/QuestConstants.java | 39 + .../game/model/inter/quest/package-info.java | 4 + .../inv/AppearanceInventoryListener.java | 45 + .../game/model/inv/FullInventoryListener.java | 55 ++ .../game/model/inv/InventoryAdapter.java | 27 + .../game/model/inv/InventoryListener.java | 32 + .../inv/SynchronizationInventoryListener.java | 57 ++ .../apollo/game/model/inv/package-info.java | 4 + .../apollo/game/model/obj/StaticObject.java | 24 + .../apollo/game/model/obj/package-info.java | 4 + src/org/apollo/game/model/package-info.java | 5 + src/org/apollo/game/model/region/Region.java | 14 + .../game/model/region/RegionCoordinates.java | 71 ++ .../game/model/region/RegionRepository.java | 9 + .../game/model/region/package-info.java | 6 + .../model/skill/LevelUpSkillListener.java | 36 + .../apollo/game/model/skill/SkillAdapter.java | 27 + .../game/model/skill/SkillListener.java | 34 + .../skill/SynchronizationSkillListener.java | 46 + .../apollo/game/model/skill/package-info.java | 4 + src/org/apollo/game/package-info.java | 4 + .../apollo/game/scheduling/ScheduledTask.java | 81 ++ src/org/apollo/game/scheduling/Scheduler.java | 52 + .../impl/SkillNormalizationTask.java | 36 + .../game/scheduling/impl/package-info.java | 4 + .../apollo/game/scheduling/package-info.java | 5 + .../apollo/game/sync/ClientSynchronizer.java | 24 + .../game/sync/ParallelClientSynchronizer.java | 78 ++ .../sync/SequentialClientSynchronizer.java | 43 + .../game/sync/block/AnimationBlock.java | 32 + .../game/sync/block/AppearanceBlock.java | 95 ++ src/org/apollo/game/sync/block/ChatBlock.java | 70 ++ .../apollo/game/sync/block/GraphicBlock.java | 32 + .../game/sync/block/SynchronizationBlock.java | 66 ++ .../sync/block/SynchronizationBlockSet.java | 76 ++ .../game/sync/block/TurnToPositionBlock.java | 32 + .../apollo/game/sync/block/package-info.java | 4 + src/org/apollo/game/sync/package-info.java | 5 + .../game/sync/seg/AddCharacterSegment.java | 55 ++ .../apollo/game/sync/seg/MovementSegment.java | 53 ++ .../game/sync/seg/RemoveCharacterSegment.java | 28 + src/org/apollo/game/sync/seg/SegmentType.java | 39 + .../game/sync/seg/SynchronizationSegment.java | 43 + .../apollo/game/sync/seg/TeleportSegment.java | 41 + .../apollo/game/sync/seg/package-info.java | 6 + .../sync/task/PhasedSynchronizationTask.java | 45 + .../sync/task/PlayerSynchronizationTask.java | 113 +++ .../task/PostPlayerSynchronizationTask.java | 39 + .../task/PrePlayerSynchronizationTask.java | 65 ++ .../game/sync/task/SynchronizationTask.java | 13 + .../apollo/game/sync/task/package-info.java | 6 + .../apollo/io/EquipmentDefinitionParser.java | 65 ++ .../apollo/io/EventHandlerChainParser.java | 109 +++ src/org/apollo/io/PluginMetaDataParser.java | 119 +++ src/org/apollo/io/package-info.java | 4 + src/org/apollo/io/player/PlayerLoader.java | 21 + .../io/player/PlayerLoaderResponse.java | 69 ++ src/org/apollo/io/player/PlayerSaver.java | 20 + .../io/player/impl/BinaryPlayerLoader.java | 130 +++ .../io/player/impl/BinaryPlayerSaver.java | 96 ++ .../io/player/impl/BinaryPlayerUtil.java | 45 + .../io/player/impl/DiscardPlayerSaver.java | 17 + .../io/player/impl/DummyPlayerLoader.java | 33 + .../io/player/impl/JdbcPlayerLoader.java | 15 + .../io/player/impl/JdbcPlayerSaver.java | 13 + .../apollo/io/player/impl/package-info.java | 4 + src/org/apollo/io/player/package-info.java | 4 + src/org/apollo/login/LoginService.java | 119 +++ src/org/apollo/login/PlayerLoaderWorker.java | 62 ++ src/org/apollo/login/PlayerSaverWorker.java | 59 ++ src/org/apollo/login/package-info.java | 4 + src/org/apollo/net/ApolloHandler.java | 104 ++ src/org/apollo/net/HttpPipelineFactory.java | 61 ++ .../apollo/net/JagGrabPipelineFactory.java | 85 ++ src/org/apollo/net/NetworkConstants.java | 41 + .../apollo/net/ServicePipelineFactory.java | 46 + src/org/apollo/net/codec/game/AccessMode.java | 20 + .../apollo/net/codec/game/DataConstants.java | 31 + src/org/apollo/net/codec/game/DataOrder.java | 30 + .../net/codec/game/DataTransformation.java | 30 + src/org/apollo/net/codec/game/DataType.java | 55 ++ .../net/codec/game/GameDecoderState.java | 30 + .../net/codec/game/GameEventDecoder.java | 43 + .../net/codec/game/GameEventEncoder.java | 44 + src/org/apollo/net/codec/game/GamePacket.java | 77 ++ .../net/codec/game/GamePacketBuilder.java | 452 +++++++++ .../net/codec/game/GamePacketDecoder.java | 161 ++++ .../net/codec/game/GamePacketEncoder.java | 65 ++ .../net/codec/game/GamePacketReader.java | 410 ++++++++ .../apollo/net/codec/game/package-info.java | 4 + .../codec/handshake/HandshakeConstants.java | 26 + .../net/codec/handshake/HandshakeDecoder.java | 63 ++ .../net/codec/handshake/HandshakeMessage.java | 30 + .../net/codec/handshake/package-info.java | 4 + .../net/codec/jaggrab/JagGrabRequest.java | 30 + .../codec/jaggrab/JagGrabRequestDecoder.java | 27 + .../net/codec/jaggrab/JagGrabResponse.java | 32 + .../codec/jaggrab/JagGrabResponseEncoder.java | 22 + .../net/codec/jaggrab/package-info.java | 4 + .../net/codec/login/LoginConstants.java | 126 +++ .../apollo/net/codec/login/LoginDecoder.java | 204 ++++ .../net/codec/login/LoginDecoderState.java | 30 + .../apollo/net/codec/login/LoginEncoder.java | 34 + .../apollo/net/codec/login/LoginRequest.java | 109 +++ .../apollo/net/codec/login/LoginResponse.java | 60 ++ .../apollo/net/codec/login/package-info.java | 4 + .../net/codec/update/OnDemandRequest.java | 129 +++ .../net/codec/update/OnDemandResponse.java | 78 ++ .../net/codec/update/UpdateDecoder.java | 31 + .../net/codec/update/UpdateEncoder.java | 38 + .../apollo/net/codec/update/package-info.java | 4 + src/org/apollo/net/meta/PacketMetaData.java | 82 ++ .../apollo/net/meta/PacketMetaDataGroup.java | 66 ++ src/org/apollo/net/meta/PacketType.java | 30 + src/org/apollo/net/meta/package-info.java | 4 + src/org/apollo/net/package-info.java | 5 + src/org/apollo/net/release/EventDecoder.java | 21 + src/org/apollo/net/release/EventEncoder.java | 21 + src/org/apollo/net/release/Release.java | 107 +++ src/org/apollo/net/release/package-info.java | 5 + .../net/release/r317/ButtonEventDecoder.java | 22 + .../r317/CharacterDesignEventDecoder.java | 38 + .../net/release/r317/ChatEventDecoder.java | 38 + .../r317/CloseInterfaceEventEncoder.java | 20 + .../r317/ClosedInterfaceEventDecoder.java | 18 + .../net/release/r317/CommandEventDecoder.java | 20 + .../release/r317/EnterAmountEventEncoder.java | 20 + .../r317/EnteredAmountEventDecoder.java | 22 + .../net/release/r317/EquipEventDecoder.java | 25 + .../r317/FifthItemActionEventDecoder.java | 27 + .../r317/FirstItemActionEventDecoder.java | 25 + .../r317/FirstObjectActionEventDecoder.java | 27 + .../r317/FourthItemActionEventDecoder.java | 25 + .../r317/IdAssignmentEventEncoder.java | 25 + .../release/r317/KeepAliveEventDecoder.java | 18 + .../net/release/r317/LogoutEventEncoder.java | 20 + .../r317/OpenInterfaceEventEncoder.java | 22 + .../OpenInterfaceSidebarEventEncoder.java | 24 + .../PlayerSynchronizationEventEncoder.java | 353 +++++++ .../r317/RegionChangeEventEncoder.java | 24 + .../apollo/net/release/r317/Release317.java | 108 +++ .../r317/SecondItemActionEventDecoder.java | 26 + .../r317/SecondObjectActionEventDecoder.java | 27 + .../r317/ServerMessageEventEncoder.java | 22 + .../release/r317/SwitchItemEventDecoder.java | 27 + .../r317/SwitchTabInterfaceEventEncoder.java | 24 + .../r317/ThirdItemActionEventDecoder.java | 26 + .../r317/ThirdObjectActionEventDecoder.java | 28 + .../release/r317/UpdateItemsEventEncoder.java | 46 + .../release/r317/UpdateSkillEventEncoder.java | 29 + .../r317/UpdateSlottedItemsEventEncoder.java | 45 + .../net/release/r317/WalkEventDecoder.java | 47 + .../apollo/net/release/r317/package-info.java | 4 + .../net/release/r377/ButtonEventDecoder.java | 22 + .../r377/CharacterDesignEventDecoder.java | 38 + .../net/release/r377/ChatEventDecoder.java | 39 + .../r377/CloseInterfaceEventEncoder.java | 20 + .../r377/ClosedInterfaceEventDecoder.java | 18 + .../net/release/r377/CommandEventDecoder.java | 20 + .../release/r377/EnterAmountEventEncoder.java | 20 + .../r377/EnteredAmountEventDecoder.java | 22 + .../net/release/r377/EquipEventDecoder.java | 26 + .../r377/FifthItemActionEventDecoder.java | 26 + .../r377/FirstItemActionEventDecoder.java | 25 + .../r377/FirstObjectActionEventDecoder.java | 27 + .../r377/FourthItemActionEventDecoder.java | 26 + .../r377/IdAssignmentEventEncoder.java | 24 + .../release/r377/KeepAliveEventDecoder.java | 18 + .../net/release/r377/LogoutEventEncoder.java | 20 + .../r377/OpenInterfaceEventEncoder.java | 24 + .../OpenInterfaceSidebarEventEncoder.java | 25 + .../PlayerSynchronizationEventEncoder.java | 354 +++++++ .../r377/RegionChangeEventEncoder.java | 25 + .../apollo/net/release/r377/Release377.java | 170 ++++ .../r377/SecondItemActionEventDecoder.java | 26 + .../r377/SecondObjectActionEventDecoder.java | 26 + .../r377/ServerMessageEventEncoder.java | 22 + .../r377/SetInterfaceTextEventEncoder.java | 26 + .../release/r377/SwitchItemEventDecoder.java | 27 + .../r377/SwitchTabInterfaceEventEncoder.java | 24 + .../r377/ThirdItemActionEventDecoder.java | 26 + .../r377/ThirdObjectActionEventDecoder.java | 27 + .../release/r377/UpdateItemsEventEncoder.java | 46 + .../release/r377/UpdateSkillEventEncoder.java | 29 + .../r377/UpdateSlottedItemsEventEncoder.java | 45 + .../net/release/r377/WalkEventDecoder.java | 48 + .../apollo/net/release/r377/package-info.java | 4 + src/org/apollo/net/session/GameSession.java | 129 +++ src/org/apollo/net/session/LoginSession.java | 135 +++ src/org/apollo/net/session/Session.java | 47 + src/org/apollo/net/session/UpdateSession.java | 51 + src/org/apollo/net/session/package-info.java | 7 + src/org/apollo/package-info.java | 5 + src/org/apollo/security/IsaacRandomPair.java | 49 + .../apollo/security/PlayerCredentials.java | 92 ++ src/org/apollo/security/package-info.java | 4 + src/org/apollo/tools/EquipmentConstants.java | 90 ++ src/org/apollo/tools/EquipmentUpdater.java | 886 ++++++++++++++++++ src/org/apollo/tools/NoteUpdater.java | 68 ++ src/org/apollo/tools/package-info.java | 4 + src/org/apollo/update/ChannelRequest.java | 58 ++ src/org/apollo/update/HttpRequestWorker.java | 143 +++ .../apollo/update/JagGrabRequestWorker.java | 47 + .../apollo/update/OnDemandRequestWorker.java | 61 ++ src/org/apollo/update/RequestWorker.java | 93 ++ src/org/apollo/update/UpdateConstants.java | 16 + src/org/apollo/update/UpdateDispatcher.java | 104 ++ src/org/apollo/update/UpdateService.java | 92 ++ src/org/apollo/update/package-info.java | 4 + .../resource/CombinedResourceProvider.java | 40 + .../resource/HypertextResourceProvider.java | 64 ++ .../update/resource/ResourceProvider.java | 30 + .../resource/VirtualResourceProvider.java | 70 ++ .../apollo/update/resource/package-info.java | 4 + src/org/apollo/util/ByteBufferUtil.java | 43 + src/org/apollo/util/ChannelBufferUtil.java | 34 + src/org/apollo/util/CharacterRepository.java | 180 ++++ src/org/apollo/util/CompressionUtil.java | 103 ++ src/org/apollo/util/EnumerationUtil.java | 48 + src/org/apollo/util/LanguageUtil.java | 27 + src/org/apollo/util/NameUtil.java | 68 ++ src/org/apollo/util/NamedThreadFactory.java | 40 + src/org/apollo/util/StatefulFrameDecoder.java | 118 +++ src/org/apollo/util/StreamUtil.java | 49 + src/org/apollo/util/TextUtil.java | 150 +++ src/org/apollo/util/package-info.java | 4 + .../util/plugin/DependencyException.java | 19 + src/org/apollo/util/plugin/PluginContext.java | 89 ++ .../apollo/util/plugin/PluginEnvironment.java | 25 + src/org/apollo/util/plugin/PluginManager.java | 167 ++++ .../apollo/util/plugin/PluginMetaData.java | 120 +++ .../util/plugin/RubyPluginEnvironment.java | 53 ++ src/org/apollo/util/plugin/package-info.java | 4 + src/org/apollo/util/xml/XmlNode.java | 221 +++++ src/org/apollo/util/xml/XmlParser.java | 151 +++ src/org/apollo/util/xml/package-info.java | 4 + .../net/codec/game/TestGamePacketEncoder.java | 66 ++ test/org/apollo/util/TestByteBufferUtil.java | 49 + .../apollo/util/TestChannelBufferUtil.java | 44 + test/org/apollo/util/TestCompressionUtil.java | 43 + test/org/apollo/util/TestLanguageUtil.java | 24 + test/org/apollo/util/TestTextUtil.java | 48 + test/org/apollo/util/xml/TestXmlParser.java | 99 ++ tools/trim.rb | 21 + 406 files changed, 23043 insertions(+) create mode 100644 LICENSE create mode 100644 README create mode 100644 build.xml create mode 100644 data/equipment-317.dat create mode 100644 data/equipment-377.dat create mode 100644 data/events.xml create mode 100644 data/login.xml create mode 100644 data/note-317.dat create mode 100644 data/note-377.dat create mode 100644 data/plugins/bank/bank.rb create mode 100644 data/plugins/bank/plugin.xml create mode 100644 data/plugins/bootstrap.rb create mode 100644 data/plugins/cmd-bank/bank.rb create mode 100644 data/plugins/cmd-bank/plugin.xml create mode 100644 data/plugins/cmd-item/item.rb create mode 100644 data/plugins/cmd-item/plugin.xml create mode 100644 data/plugins/cmd-skill/plugin.xml create mode 100644 data/plugins/cmd-skill/skill.rb create mode 100644 data/plugins/cmd-teleport/plugin.xml create mode 100644 data/plugins/cmd-teleport/teleport.rb create mode 100644 data/plugins/dummy/dummy.rb create mode 100644 data/plugins/dummy/plugin.xml create mode 100644 data/plugins/hello/hello.rb create mode 100644 data/plugins/hello/plugin.xml create mode 100644 data/plugins/logout/logout.rb create mode 100644 data/plugins/logout/plugin.xml create mode 100644 data/plugins/mining/gem.rb create mode 100644 data/plugins/mining/mining.rb create mode 100644 data/plugins/mining/ore.rb create mode 100644 data/plugins/mining/pickaxe.rb create mode 100644 data/plugins/mining/plugin.xml create mode 100644 data/plugins/mining/respawn.rb create mode 100644 data/services.xml create mode 100644 data/synchronizer.xml create mode 100644 etc/jsr166.jar create mode 100644 etc/logo/logo.png create mode 100644 etc/logo/logo.pspimage create mode 100644 etc/logo/logo_trans.png create mode 100644 etc/logo/logo_trans.pspimage create mode 100644 etc/logo/logo_trans_alt.png create mode 100644 etc/logo/logo_trans_alt.pspimage create mode 100644 etc/logo/logo_trans_alt_sm.png create mode 100644 ivy.xml create mode 100644 ivysettings.xml create mode 100644 src/net/burtleburtle/bob/rand/IsaacRandom.java create mode 100644 src/net/burtleburtle/bob/rand/package-info.java create mode 100644 src/org/apollo/Server.java create mode 100644 src/org/apollo/ServerContext.java create mode 100644 src/org/apollo/Service.java create mode 100644 src/org/apollo/ServiceManager.java create mode 100644 src/org/apollo/fs/FileDescriptor.java create mode 100644 src/org/apollo/fs/FileSystemConstants.java create mode 100644 src/org/apollo/fs/Index.java create mode 100644 src/org/apollo/fs/IndexedFileSystem.java create mode 100644 src/org/apollo/fs/archive/Archive.java create mode 100644 src/org/apollo/fs/archive/ArchiveEntry.java create mode 100644 src/org/apollo/fs/archive/package-info.java create mode 100644 src/org/apollo/fs/package-info.java create mode 100644 src/org/apollo/fs/parser/ItemDefinitionParser.java create mode 100644 src/org/apollo/fs/parser/package-info.java create mode 100644 src/org/apollo/game/GameConstants.java create mode 100644 src/org/apollo/game/GamePulseHandler.java create mode 100644 src/org/apollo/game/GameService.java create mode 100644 src/org/apollo/game/action/Action.java create mode 100644 src/org/apollo/game/action/DistancedAction.java create mode 100644 src/org/apollo/game/action/package-info.java create mode 100644 src/org/apollo/game/command/Command.java create mode 100644 src/org/apollo/game/command/CommandDispatcher.java create mode 100644 src/org/apollo/game/command/CommandListener.java create mode 100644 src/org/apollo/game/command/CreditsCommandListener.java create mode 100644 src/org/apollo/game/command/PrivilegedCommandListener.java create mode 100644 src/org/apollo/game/command/package-info.java create mode 100644 src/org/apollo/game/event/Event.java create mode 100644 src/org/apollo/game/event/handler/EventHandler.java create mode 100644 src/org/apollo/game/event/handler/EventHandlerContext.java create mode 100644 src/org/apollo/game/event/handler/chain/EventHandlerChain.java create mode 100644 src/org/apollo/game/event/handler/chain/EventHandlerChainGroup.java create mode 100644 src/org/apollo/game/event/handler/chain/package-info.java create mode 100644 src/org/apollo/game/event/handler/impl/BankButtonEventHandler.java create mode 100644 src/org/apollo/game/event/handler/impl/BankEventHandler.java create mode 100644 src/org/apollo/game/event/handler/impl/CharacterDesignEventHandler.java create mode 100644 src/org/apollo/game/event/handler/impl/CharacterDesignVerificationHandler.java create mode 100644 src/org/apollo/game/event/handler/impl/ChatEventHandler.java create mode 100644 src/org/apollo/game/event/handler/impl/ChatVerificationHandler.java create mode 100644 src/org/apollo/game/event/handler/impl/ClosedInterfaceEventHandler.java create mode 100644 src/org/apollo/game/event/handler/impl/CommandEventHandler.java create mode 100644 src/org/apollo/game/event/handler/impl/EnteredAmountEventHandler.java create mode 100644 src/org/apollo/game/event/handler/impl/EquipEventHandler.java create mode 100644 src/org/apollo/game/event/handler/impl/RemoveEventHandler.java create mode 100644 src/org/apollo/game/event/handler/impl/SwitchItemEventHandler.java create mode 100644 src/org/apollo/game/event/handler/impl/WalkEventHandler.java create mode 100644 src/org/apollo/game/event/handler/impl/package-info.java create mode 100644 src/org/apollo/game/event/handler/package-info.java create mode 100644 src/org/apollo/game/event/impl/ButtonEvent.java create mode 100644 src/org/apollo/game/event/impl/CharacterDesignEvent.java create mode 100644 src/org/apollo/game/event/impl/ChatEvent.java create mode 100644 src/org/apollo/game/event/impl/CloseInterfaceEvent.java create mode 100644 src/org/apollo/game/event/impl/ClosedInterfaceEvent.java create mode 100644 src/org/apollo/game/event/impl/CommandEvent.java create mode 100644 src/org/apollo/game/event/impl/EnterAmountEvent.java create mode 100644 src/org/apollo/game/event/impl/EnteredAmountEvent.java create mode 100644 src/org/apollo/game/event/impl/EquipEvent.java create mode 100644 src/org/apollo/game/event/impl/FifthItemActionEvent.java create mode 100644 src/org/apollo/game/event/impl/FirstItemActionEvent.java create mode 100644 src/org/apollo/game/event/impl/FirstObjectActionEvent.java create mode 100644 src/org/apollo/game/event/impl/FourthItemActionEvent.java create mode 100644 src/org/apollo/game/event/impl/IdAssignmentEvent.java create mode 100644 src/org/apollo/game/event/impl/ItemActionEvent.java create mode 100644 src/org/apollo/game/event/impl/KeepAliveEvent.java create mode 100644 src/org/apollo/game/event/impl/LogoutEvent.java create mode 100644 src/org/apollo/game/event/impl/ObjectActionEvent.java create mode 100644 src/org/apollo/game/event/impl/OpenInterfaceEvent.java create mode 100644 src/org/apollo/game/event/impl/OpenInterfaceSidebarEvent.java create mode 100644 src/org/apollo/game/event/impl/PlayerSynchronizationEvent.java create mode 100644 src/org/apollo/game/event/impl/RegionChangeEvent.java create mode 100644 src/org/apollo/game/event/impl/SecondItemActionEvent.java create mode 100644 src/org/apollo/game/event/impl/SecondObjectActionEvent.java create mode 100644 src/org/apollo/game/event/impl/ServerMessageEvent.java create mode 100644 src/org/apollo/game/event/impl/SetInterfaceTextEvent.java create mode 100644 src/org/apollo/game/event/impl/SwitchItemEvent.java create mode 100644 src/org/apollo/game/event/impl/SwitchTabInterfaceEvent.java create mode 100644 src/org/apollo/game/event/impl/ThirdItemActionEvent.java create mode 100644 src/org/apollo/game/event/impl/ThirdObjectActionEvent.java create mode 100644 src/org/apollo/game/event/impl/UpdateItemsEvent.java create mode 100644 src/org/apollo/game/event/impl/UpdateSkillEvent.java create mode 100644 src/org/apollo/game/event/impl/UpdateSlottedItemsEvent.java create mode 100644 src/org/apollo/game/event/impl/WalkEvent.java create mode 100644 src/org/apollo/game/event/impl/package-info.java create mode 100644 src/org/apollo/game/event/package-info.java create mode 100644 src/org/apollo/game/model/Animation.java create mode 100644 src/org/apollo/game/model/Appearance.java create mode 100644 src/org/apollo/game/model/Character.java create mode 100644 src/org/apollo/game/model/Direction.java create mode 100644 src/org/apollo/game/model/EquipmentConstants.java create mode 100644 src/org/apollo/game/model/Gender.java create mode 100644 src/org/apollo/game/model/Graphic.java create mode 100644 src/org/apollo/game/model/InterfaceSet.java create mode 100644 src/org/apollo/game/model/InterfaceType.java create mode 100644 src/org/apollo/game/model/Inventory.java create mode 100644 src/org/apollo/game/model/InventoryConstants.java create mode 100644 src/org/apollo/game/model/Item.java create mode 100644 src/org/apollo/game/model/Player.java create mode 100644 src/org/apollo/game/model/Position.java create mode 100644 src/org/apollo/game/model/Skill.java create mode 100644 src/org/apollo/game/model/SkillSet.java create mode 100644 src/org/apollo/game/model/SlottedItem.java create mode 100644 src/org/apollo/game/model/WalkingQueue.java create mode 100644 src/org/apollo/game/model/World.java create mode 100644 src/org/apollo/game/model/WorldConstants.java create mode 100644 src/org/apollo/game/model/def/EquipmentDefinition.java create mode 100644 src/org/apollo/game/model/def/ItemDefinition.java create mode 100644 src/org/apollo/game/model/def/StaticObjectDefinition.java create mode 100644 src/org/apollo/game/model/def/package-info.java create mode 100644 src/org/apollo/game/model/inter/EnterAmountListener.java create mode 100644 src/org/apollo/game/model/inter/InterfaceListener.java create mode 100644 src/org/apollo/game/model/inter/bank/BankConstants.java create mode 100644 src/org/apollo/game/model/inter/bank/BankDepositEnterAmountListener.java create mode 100644 src/org/apollo/game/model/inter/bank/BankInterfaceListener.java create mode 100644 src/org/apollo/game/model/inter/bank/BankUtils.java create mode 100644 src/org/apollo/game/model/inter/bank/BankWithdrawEnterAmountListener.java create mode 100644 src/org/apollo/game/model/inter/bank/package-info.java create mode 100644 src/org/apollo/game/model/inter/package-info.java create mode 100644 src/org/apollo/game/model/inter/quest/QuestConstants.java create mode 100644 src/org/apollo/game/model/inter/quest/package-info.java create mode 100644 src/org/apollo/game/model/inv/AppearanceInventoryListener.java create mode 100644 src/org/apollo/game/model/inv/FullInventoryListener.java create mode 100644 src/org/apollo/game/model/inv/InventoryAdapter.java create mode 100644 src/org/apollo/game/model/inv/InventoryListener.java create mode 100644 src/org/apollo/game/model/inv/SynchronizationInventoryListener.java create mode 100644 src/org/apollo/game/model/inv/package-info.java create mode 100644 src/org/apollo/game/model/obj/StaticObject.java create mode 100644 src/org/apollo/game/model/obj/package-info.java create mode 100644 src/org/apollo/game/model/package-info.java create mode 100644 src/org/apollo/game/model/region/Region.java create mode 100644 src/org/apollo/game/model/region/RegionCoordinates.java create mode 100644 src/org/apollo/game/model/region/RegionRepository.java create mode 100644 src/org/apollo/game/model/region/package-info.java create mode 100644 src/org/apollo/game/model/skill/LevelUpSkillListener.java create mode 100644 src/org/apollo/game/model/skill/SkillAdapter.java create mode 100644 src/org/apollo/game/model/skill/SkillListener.java create mode 100644 src/org/apollo/game/model/skill/SynchronizationSkillListener.java create mode 100644 src/org/apollo/game/model/skill/package-info.java create mode 100644 src/org/apollo/game/package-info.java create mode 100644 src/org/apollo/game/scheduling/ScheduledTask.java create mode 100644 src/org/apollo/game/scheduling/Scheduler.java create mode 100644 src/org/apollo/game/scheduling/impl/SkillNormalizationTask.java create mode 100644 src/org/apollo/game/scheduling/impl/package-info.java create mode 100644 src/org/apollo/game/scheduling/package-info.java create mode 100644 src/org/apollo/game/sync/ClientSynchronizer.java create mode 100644 src/org/apollo/game/sync/ParallelClientSynchronizer.java create mode 100644 src/org/apollo/game/sync/SequentialClientSynchronizer.java create mode 100644 src/org/apollo/game/sync/block/AnimationBlock.java create mode 100644 src/org/apollo/game/sync/block/AppearanceBlock.java create mode 100644 src/org/apollo/game/sync/block/ChatBlock.java create mode 100644 src/org/apollo/game/sync/block/GraphicBlock.java create mode 100644 src/org/apollo/game/sync/block/SynchronizationBlock.java create mode 100644 src/org/apollo/game/sync/block/SynchronizationBlockSet.java create mode 100644 src/org/apollo/game/sync/block/TurnToPositionBlock.java create mode 100644 src/org/apollo/game/sync/block/package-info.java create mode 100644 src/org/apollo/game/sync/package-info.java create mode 100644 src/org/apollo/game/sync/seg/AddCharacterSegment.java create mode 100644 src/org/apollo/game/sync/seg/MovementSegment.java create mode 100644 src/org/apollo/game/sync/seg/RemoveCharacterSegment.java create mode 100644 src/org/apollo/game/sync/seg/SegmentType.java create mode 100644 src/org/apollo/game/sync/seg/SynchronizationSegment.java create mode 100644 src/org/apollo/game/sync/seg/TeleportSegment.java create mode 100644 src/org/apollo/game/sync/seg/package-info.java create mode 100644 src/org/apollo/game/sync/task/PhasedSynchronizationTask.java create mode 100644 src/org/apollo/game/sync/task/PlayerSynchronizationTask.java create mode 100644 src/org/apollo/game/sync/task/PostPlayerSynchronizationTask.java create mode 100644 src/org/apollo/game/sync/task/PrePlayerSynchronizationTask.java create mode 100644 src/org/apollo/game/sync/task/SynchronizationTask.java create mode 100644 src/org/apollo/game/sync/task/package-info.java create mode 100644 src/org/apollo/io/EquipmentDefinitionParser.java create mode 100644 src/org/apollo/io/EventHandlerChainParser.java create mode 100644 src/org/apollo/io/PluginMetaDataParser.java create mode 100644 src/org/apollo/io/package-info.java create mode 100644 src/org/apollo/io/player/PlayerLoader.java create mode 100644 src/org/apollo/io/player/PlayerLoaderResponse.java create mode 100644 src/org/apollo/io/player/PlayerSaver.java create mode 100644 src/org/apollo/io/player/impl/BinaryPlayerLoader.java create mode 100644 src/org/apollo/io/player/impl/BinaryPlayerSaver.java create mode 100644 src/org/apollo/io/player/impl/BinaryPlayerUtil.java create mode 100644 src/org/apollo/io/player/impl/DiscardPlayerSaver.java create mode 100644 src/org/apollo/io/player/impl/DummyPlayerLoader.java create mode 100644 src/org/apollo/io/player/impl/JdbcPlayerLoader.java create mode 100644 src/org/apollo/io/player/impl/JdbcPlayerSaver.java create mode 100644 src/org/apollo/io/player/impl/package-info.java create mode 100644 src/org/apollo/io/player/package-info.java create mode 100644 src/org/apollo/login/LoginService.java create mode 100644 src/org/apollo/login/PlayerLoaderWorker.java create mode 100644 src/org/apollo/login/PlayerSaverWorker.java create mode 100644 src/org/apollo/login/package-info.java create mode 100644 src/org/apollo/net/ApolloHandler.java create mode 100644 src/org/apollo/net/HttpPipelineFactory.java create mode 100644 src/org/apollo/net/JagGrabPipelineFactory.java create mode 100644 src/org/apollo/net/NetworkConstants.java create mode 100644 src/org/apollo/net/ServicePipelineFactory.java create mode 100644 src/org/apollo/net/codec/game/AccessMode.java create mode 100644 src/org/apollo/net/codec/game/DataConstants.java create mode 100644 src/org/apollo/net/codec/game/DataOrder.java create mode 100644 src/org/apollo/net/codec/game/DataTransformation.java create mode 100644 src/org/apollo/net/codec/game/DataType.java create mode 100644 src/org/apollo/net/codec/game/GameDecoderState.java create mode 100644 src/org/apollo/net/codec/game/GameEventDecoder.java create mode 100644 src/org/apollo/net/codec/game/GameEventEncoder.java create mode 100644 src/org/apollo/net/codec/game/GamePacket.java create mode 100644 src/org/apollo/net/codec/game/GamePacketBuilder.java create mode 100644 src/org/apollo/net/codec/game/GamePacketDecoder.java create mode 100644 src/org/apollo/net/codec/game/GamePacketEncoder.java create mode 100644 src/org/apollo/net/codec/game/GamePacketReader.java create mode 100644 src/org/apollo/net/codec/game/package-info.java create mode 100644 src/org/apollo/net/codec/handshake/HandshakeConstants.java create mode 100644 src/org/apollo/net/codec/handshake/HandshakeDecoder.java create mode 100644 src/org/apollo/net/codec/handshake/HandshakeMessage.java create mode 100644 src/org/apollo/net/codec/handshake/package-info.java create mode 100644 src/org/apollo/net/codec/jaggrab/JagGrabRequest.java create mode 100644 src/org/apollo/net/codec/jaggrab/JagGrabRequestDecoder.java create mode 100644 src/org/apollo/net/codec/jaggrab/JagGrabResponse.java create mode 100644 src/org/apollo/net/codec/jaggrab/JagGrabResponseEncoder.java create mode 100644 src/org/apollo/net/codec/jaggrab/package-info.java create mode 100644 src/org/apollo/net/codec/login/LoginConstants.java create mode 100644 src/org/apollo/net/codec/login/LoginDecoder.java create mode 100644 src/org/apollo/net/codec/login/LoginDecoderState.java create mode 100644 src/org/apollo/net/codec/login/LoginEncoder.java create mode 100644 src/org/apollo/net/codec/login/LoginRequest.java create mode 100644 src/org/apollo/net/codec/login/LoginResponse.java create mode 100644 src/org/apollo/net/codec/login/package-info.java create mode 100644 src/org/apollo/net/codec/update/OnDemandRequest.java create mode 100644 src/org/apollo/net/codec/update/OnDemandResponse.java create mode 100644 src/org/apollo/net/codec/update/UpdateDecoder.java create mode 100644 src/org/apollo/net/codec/update/UpdateEncoder.java create mode 100644 src/org/apollo/net/codec/update/package-info.java create mode 100644 src/org/apollo/net/meta/PacketMetaData.java create mode 100644 src/org/apollo/net/meta/PacketMetaDataGroup.java create mode 100644 src/org/apollo/net/meta/PacketType.java create mode 100644 src/org/apollo/net/meta/package-info.java create mode 100644 src/org/apollo/net/package-info.java create mode 100644 src/org/apollo/net/release/EventDecoder.java create mode 100644 src/org/apollo/net/release/EventEncoder.java create mode 100644 src/org/apollo/net/release/Release.java create mode 100644 src/org/apollo/net/release/package-info.java create mode 100644 src/org/apollo/net/release/r317/ButtonEventDecoder.java create mode 100644 src/org/apollo/net/release/r317/CharacterDesignEventDecoder.java create mode 100644 src/org/apollo/net/release/r317/ChatEventDecoder.java create mode 100644 src/org/apollo/net/release/r317/CloseInterfaceEventEncoder.java create mode 100644 src/org/apollo/net/release/r317/ClosedInterfaceEventDecoder.java create mode 100644 src/org/apollo/net/release/r317/CommandEventDecoder.java create mode 100644 src/org/apollo/net/release/r317/EnterAmountEventEncoder.java create mode 100644 src/org/apollo/net/release/r317/EnteredAmountEventDecoder.java create mode 100644 src/org/apollo/net/release/r317/EquipEventDecoder.java create mode 100644 src/org/apollo/net/release/r317/FifthItemActionEventDecoder.java create mode 100644 src/org/apollo/net/release/r317/FirstItemActionEventDecoder.java create mode 100644 src/org/apollo/net/release/r317/FirstObjectActionEventDecoder.java create mode 100644 src/org/apollo/net/release/r317/FourthItemActionEventDecoder.java create mode 100644 src/org/apollo/net/release/r317/IdAssignmentEventEncoder.java create mode 100644 src/org/apollo/net/release/r317/KeepAliveEventDecoder.java create mode 100644 src/org/apollo/net/release/r317/LogoutEventEncoder.java create mode 100644 src/org/apollo/net/release/r317/OpenInterfaceEventEncoder.java create mode 100644 src/org/apollo/net/release/r317/OpenInterfaceSidebarEventEncoder.java create mode 100644 src/org/apollo/net/release/r317/PlayerSynchronizationEventEncoder.java create mode 100644 src/org/apollo/net/release/r317/RegionChangeEventEncoder.java create mode 100644 src/org/apollo/net/release/r317/Release317.java create mode 100644 src/org/apollo/net/release/r317/SecondItemActionEventDecoder.java create mode 100644 src/org/apollo/net/release/r317/SecondObjectActionEventDecoder.java create mode 100644 src/org/apollo/net/release/r317/ServerMessageEventEncoder.java create mode 100644 src/org/apollo/net/release/r317/SwitchItemEventDecoder.java create mode 100644 src/org/apollo/net/release/r317/SwitchTabInterfaceEventEncoder.java create mode 100644 src/org/apollo/net/release/r317/ThirdItemActionEventDecoder.java create mode 100644 src/org/apollo/net/release/r317/ThirdObjectActionEventDecoder.java create mode 100644 src/org/apollo/net/release/r317/UpdateItemsEventEncoder.java create mode 100644 src/org/apollo/net/release/r317/UpdateSkillEventEncoder.java create mode 100644 src/org/apollo/net/release/r317/UpdateSlottedItemsEventEncoder.java create mode 100644 src/org/apollo/net/release/r317/WalkEventDecoder.java create mode 100644 src/org/apollo/net/release/r317/package-info.java create mode 100644 src/org/apollo/net/release/r377/ButtonEventDecoder.java create mode 100644 src/org/apollo/net/release/r377/CharacterDesignEventDecoder.java create mode 100644 src/org/apollo/net/release/r377/ChatEventDecoder.java create mode 100644 src/org/apollo/net/release/r377/CloseInterfaceEventEncoder.java create mode 100644 src/org/apollo/net/release/r377/ClosedInterfaceEventDecoder.java create mode 100644 src/org/apollo/net/release/r377/CommandEventDecoder.java create mode 100644 src/org/apollo/net/release/r377/EnterAmountEventEncoder.java create mode 100644 src/org/apollo/net/release/r377/EnteredAmountEventDecoder.java create mode 100644 src/org/apollo/net/release/r377/EquipEventDecoder.java create mode 100644 src/org/apollo/net/release/r377/FifthItemActionEventDecoder.java create mode 100644 src/org/apollo/net/release/r377/FirstItemActionEventDecoder.java create mode 100644 src/org/apollo/net/release/r377/FirstObjectActionEventDecoder.java create mode 100644 src/org/apollo/net/release/r377/FourthItemActionEventDecoder.java create mode 100644 src/org/apollo/net/release/r377/IdAssignmentEventEncoder.java create mode 100644 src/org/apollo/net/release/r377/KeepAliveEventDecoder.java create mode 100644 src/org/apollo/net/release/r377/LogoutEventEncoder.java create mode 100644 src/org/apollo/net/release/r377/OpenInterfaceEventEncoder.java create mode 100644 src/org/apollo/net/release/r377/OpenInterfaceSidebarEventEncoder.java create mode 100644 src/org/apollo/net/release/r377/PlayerSynchronizationEventEncoder.java create mode 100644 src/org/apollo/net/release/r377/RegionChangeEventEncoder.java create mode 100644 src/org/apollo/net/release/r377/Release377.java create mode 100644 src/org/apollo/net/release/r377/SecondItemActionEventDecoder.java create mode 100644 src/org/apollo/net/release/r377/SecondObjectActionEventDecoder.java create mode 100644 src/org/apollo/net/release/r377/ServerMessageEventEncoder.java create mode 100644 src/org/apollo/net/release/r377/SetInterfaceTextEventEncoder.java create mode 100644 src/org/apollo/net/release/r377/SwitchItemEventDecoder.java create mode 100644 src/org/apollo/net/release/r377/SwitchTabInterfaceEventEncoder.java create mode 100644 src/org/apollo/net/release/r377/ThirdItemActionEventDecoder.java create mode 100644 src/org/apollo/net/release/r377/ThirdObjectActionEventDecoder.java create mode 100644 src/org/apollo/net/release/r377/UpdateItemsEventEncoder.java create mode 100644 src/org/apollo/net/release/r377/UpdateSkillEventEncoder.java create mode 100644 src/org/apollo/net/release/r377/UpdateSlottedItemsEventEncoder.java create mode 100644 src/org/apollo/net/release/r377/WalkEventDecoder.java create mode 100644 src/org/apollo/net/release/r377/package-info.java create mode 100644 src/org/apollo/net/session/GameSession.java create mode 100644 src/org/apollo/net/session/LoginSession.java create mode 100644 src/org/apollo/net/session/Session.java create mode 100644 src/org/apollo/net/session/UpdateSession.java create mode 100644 src/org/apollo/net/session/package-info.java create mode 100644 src/org/apollo/package-info.java create mode 100644 src/org/apollo/security/IsaacRandomPair.java create mode 100644 src/org/apollo/security/PlayerCredentials.java create mode 100644 src/org/apollo/security/package-info.java create mode 100644 src/org/apollo/tools/EquipmentConstants.java create mode 100644 src/org/apollo/tools/EquipmentUpdater.java create mode 100644 src/org/apollo/tools/NoteUpdater.java create mode 100644 src/org/apollo/tools/package-info.java create mode 100644 src/org/apollo/update/ChannelRequest.java create mode 100644 src/org/apollo/update/HttpRequestWorker.java create mode 100644 src/org/apollo/update/JagGrabRequestWorker.java create mode 100644 src/org/apollo/update/OnDemandRequestWorker.java create mode 100644 src/org/apollo/update/RequestWorker.java create mode 100644 src/org/apollo/update/UpdateConstants.java create mode 100644 src/org/apollo/update/UpdateDispatcher.java create mode 100644 src/org/apollo/update/UpdateService.java create mode 100644 src/org/apollo/update/package-info.java create mode 100644 src/org/apollo/update/resource/CombinedResourceProvider.java create mode 100644 src/org/apollo/update/resource/HypertextResourceProvider.java create mode 100644 src/org/apollo/update/resource/ResourceProvider.java create mode 100644 src/org/apollo/update/resource/VirtualResourceProvider.java create mode 100644 src/org/apollo/update/resource/package-info.java create mode 100644 src/org/apollo/util/ByteBufferUtil.java create mode 100644 src/org/apollo/util/ChannelBufferUtil.java create mode 100644 src/org/apollo/util/CharacterRepository.java create mode 100644 src/org/apollo/util/CompressionUtil.java create mode 100644 src/org/apollo/util/EnumerationUtil.java create mode 100644 src/org/apollo/util/LanguageUtil.java create mode 100644 src/org/apollo/util/NameUtil.java create mode 100644 src/org/apollo/util/NamedThreadFactory.java create mode 100644 src/org/apollo/util/StatefulFrameDecoder.java create mode 100644 src/org/apollo/util/StreamUtil.java create mode 100644 src/org/apollo/util/TextUtil.java create mode 100644 src/org/apollo/util/package-info.java create mode 100644 src/org/apollo/util/plugin/DependencyException.java create mode 100644 src/org/apollo/util/plugin/PluginContext.java create mode 100644 src/org/apollo/util/plugin/PluginEnvironment.java create mode 100644 src/org/apollo/util/plugin/PluginManager.java create mode 100644 src/org/apollo/util/plugin/PluginMetaData.java create mode 100644 src/org/apollo/util/plugin/RubyPluginEnvironment.java create mode 100644 src/org/apollo/util/plugin/package-info.java create mode 100644 src/org/apollo/util/xml/XmlNode.java create mode 100644 src/org/apollo/util/xml/XmlParser.java create mode 100644 src/org/apollo/util/xml/package-info.java create mode 100644 test/org/apollo/net/codec/game/TestGamePacketEncoder.java create mode 100644 test/org/apollo/util/TestByteBufferUtil.java create mode 100644 test/org/apollo/util/TestChannelBufferUtil.java create mode 100644 test/org/apollo/util/TestCompressionUtil.java create mode 100644 test/org/apollo/util/TestLanguageUtil.java create mode 100644 test/org/apollo/util/TestTextUtil.java create mode 100644 test/org/apollo/util/xml/TestXmlParser.java create mode 100644 tools/trim.rb diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..dbc7d5340 --- /dev/null +++ b/LICENSE @@ -0,0 +1,14 @@ +Copyright (c) 2010-2011 Graham Edgecombe + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + diff --git a/README b/README new file mode 100644 index 000000000..61aa60f68 --- /dev/null +++ b/README @@ -0,0 +1,55 @@ +Apollo is a RuneScape emulator which aims to encourage a fundamentally-different alternative to the way in which private server development is done today. It consists of a high-performance, modular server written in Java as well as a collection of utilities for managing the data files and plugins. + +Currently people download a server, read through tutorials and apply their modifications or write their own code on top of it. They'll then host it or release it. The result is we currently have a complete mess of servers (this includes every current base - Hyperion, rs2hd, winterlove) all created from cobbled-together code. + +Apollo is going to change that through its plugin system. Instead of throwing everything into one big application, Apollo consists of: + + A small 'core' application which provides features necessary for the server to operate. + A set of plugins, and the tools to manage them (install, uninstall, publish, download, etc). + Some additional tools and utilities for managing the data files. + + +This will make it much easier for everyone to develop a private server, no longer restricting it to people who can (or can't as the case is here) program. Instead of messing around copying and pasting lines of code from a woodcutting tutorial, people will simply have to download and install a woodcutting plugin. + +There is also a plan to have a plugin repository (or maybe multiple repositories) and a set of tools to make the experience very much like a package manager on Linux. + +It also means updates can be provided for the core server (e.g. security, stability, optimizations) very easily. Users will just have to download the new jar, overwrite the current one with the new one, and reboot their server. + +And best of all, inexperienced users are protected from making fatal mistakes in the core and are kept away from stuff that they shouldn't be editing at their experience level. + +Plugins are currently written in Ruby, however, in the future other languages could be added. + +Core Features + +Some of the significant (technically) current core features include: + + Packet encoding/decoding has been split from the representations of the packets themselves. This allows the potential for encoding/decoding to go on in parallel and also allows multiple revisions to be supported. Currently 317 and 377 are both completely supported. + Update server support (JAGGRAB, ondemand and HTTP). + Packet handler chaining: this allows multiple plugins to be able to intercept a single packet and deal with it appropriately. For example, a quest plugin could intercept searching a bookshelf for instance, if the behaviour needed to change in certain cases. + Parallel execution of player updating for multi-core machines - this has a significant benefit on my dual core machine used to test the server. + + +As well as that, it has the bog standard stuff: + + Login + Appearance updating + Multiplayer + Walking/running + Rights management + Travel back algorithm for movement + Character design + Chatting + Commands + Inventory support + Equipment support + Animations + Graphics + Facing/turn to + Action system + Working distanced actions + All data types implemented + Task scheduler based on game ticks + Saving/loading with a custom binary format + Skill levels/experiences + Plugin management + Reads item information from the cache \ No newline at end of file diff --git a/build.xml b/build.xml new file mode 100644 index 000000000..bb46a792f --- /dev/null +++ b/build.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Apollo]]> + + + + + + + + + + + + + + + + diff --git a/data/equipment-317.dat b/data/equipment-317.dat new file mode 100644 index 0000000000000000000000000000000000000000..5d5eb20a0dff72525c74cfd77bdfd8ed5c79bee6 GIT binary patch literal 22122 zcmeI2F?TB`5Jt1fdnYIFB%PZyuGOSbty=#szghW!feXv*YFSCP^&%Jc-C-Dp0S1=! z-~V0)e}4N#KU4Twr^{bO(Q6?TCNnCXsbW}VKZ+vc8GKsVucd>-UHG$9U8iBg3l;Xd z%7A_>wcYsGw%1#0JzIa;67?_<(sF{b)xLhU-oBTLS-tzqx_cab`(-l!A4k8Ju0D>4 z{^RJv)au5nDys&0twL|2mC%nU#}w37xG<&G2?LN!@jy~z3ja|C@d8586`GxShUrl{ zT-oh#~sfopY zUwWTw#4G#F*G+7Pky~rTF0(F*u6d80VGRUjz$?yR3^k@yj zQwR?r&))P_`UV|}Y?{860p3mW)apX&rO6<9Z!$>Un=F#1mUq4=$D1)M> zQ3l}wUPWINBJ(N|y`=Q*E%yUD9I{OILd}wJ_K|vIRv`7rWDu=Er=ka`A67R}axcjC zQSvI(g_2i61|_e848j9Z+%RH{-m-@>$!YMer1s1nB>znY$$yhU^50~T;%hQUT{YP_ z;T42e5MDueCE9k>S}(ud2+rn56kFG!d$e4s6Eb9x zmJG5iCK+T)Q8EZC5LO_pKv;pyicaQ`S&vmzN} zH6(+~vSbieAgn-Gfv^Husj@JX=1q8>Z%kWd%NFn&!fVJHzZ#E{)SGCA_OTCPAKKnh z>eVy;_R^2j51SU63@YRS^8Z88;;U+q#kbkrz1fqq`&4D}oTcjAgk|W}MjaudMuebS zV&%+~naCU2uP&Q)Vf$k|>uA&HzQN37mRJ^+1rwivyW#_fEQl(YStn|U($YR2yvz@?B>TyRL`Q4PV&zpfrR*25D!px=_073wph?mFIQl zti)dm!!*@Cf0zzXKKo7wsO*>r$R@GsLU-<*HKt+fNJC^Ys5u4{N3)&=5}74O)qp7V zC`GFJBConO#0}j!1Rr3|di#JV6Gy)rB&!Se3BKxr)TXV**65#mQFR8&ZL_03j&fV+ zF9Cwesh0^&_ZwoHfm-`|;&|8mvEkxj#ftcGNe4ZmcNO(@BrpQQ~J%O?8n9Ho^FySh3mbTaKeolN_I z$wb{;VZp`Oan_PeUojJfL>A6u>7NcFxvP+=1z}pPhQSQB=Wq8#{RmNUovbs>bmal+ zI-LdiFn8Yj;Wl=tIPO}p<)v`4&>l`|E%$9U=0wHYHF97!{q+rmaW0oNM!_6OAL{0+ z;wja*d&bbds97-HB6`W{l=6zk6Gel>xt4D#tv$zb5?<3tWwrWzL}I>HPMQnVbDpz9 zjutIcnGdrw<;KRB`2%WDtK-X~&*@enC_pin(j&;cY>sxZG~Fr$E33B(tyiN@v88ah zNE>IQh2d&VGfYq(hVyim*I__Jv4wDwsmQi1l|mK=$)I8-Y(mLi+EmZMVBB6K6)XAW zUB$&pMY0mcUZqg7BB|L6G+-~N%ZFq*P~P-w%J#ySLaD|Y*}wATQFZfKYdg^oh`2zx z-MVktgF04Nfo_RVJ871la=)h%0iTxYBd;bSLZi^5wDiX*nXo7_Lk7sAts3Jr?k_d6 z=eh58hZjBHCL8yc8u&3z&+mC$>jQIy`9%cBX*|-@c*R`9wPS@YoG=J1RNtW~+!T1k zIRVOJw10Lb3*K*98K?StuxI-vt0TmV_g9aLzo#PQya=uLi98qPd{}Y1bnxyZ=4OJn z^mXrM+nC4_q-d_)KVPfA^&oO@z1~w_GQKGP=QU<8QBM(#ujKR0m-_#r*JJ6=-5-?2 zuj53I#;J>e1oX2CL*12HL;Cb5)KpjXcZFQ@Y!ZM;TkX V;#fL6-c+)9qZ^8o*OZJ~_z%$#M4A8q literal 0 HcmV?d00001 diff --git a/data/equipment-377.dat b/data/equipment-377.dat new file mode 100644 index 0000000000000000000000000000000000000000..7926545959e19fdb24c43eec960db04ed4e2cf44 GIT binary patch literal 25625 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 + + org.apollo.game.event.impl.KeepAliveEvent + + + + org.apollo.game.event.impl.CharacterDesignEvent + + org.apollo.game.event.handler.impl.CharacterDesignVerificationHandler + org.apollo.game.event.handler.impl.CharacterDesignEventHandler + + + + org.apollo.game.event.impl.WalkEvent + + org.apollo.game.event.handler.impl.WalkEventHandler + + + + org.apollo.game.event.impl.ChatEvent + + org.apollo.game.event.handler.impl.ChatVerificationHandler + org.apollo.game.event.handler.impl.ChatEventHandler + + + + org.apollo.game.event.impl.ButtonEvent + + org.apollo.game.event.handler.impl.BankButtonEventHandler + + + + org.apollo.game.event.impl.CommandEvent + + org.apollo.game.event.handler.impl.CommandEventHandler + + + + org.apollo.game.event.impl.SwitchItemEvent + + org.apollo.game.event.handler.impl.SwitchItemEventHandler + + + + org.apollo.game.event.impl.ObjectActionEvent + + + + org.apollo.game.event.impl.EquipEvent + + org.apollo.game.event.handler.impl.EquipEventHandler + + + + org.apollo.game.event.impl.ItemActionEvent + + org.apollo.game.event.handler.impl.RemoveEventHandler + org.apollo.game.event.handler.impl.BankEventHandler + + + + org.apollo.game.event.impl.ClosedInterfaceEvent + + org.apollo.game.event.handler.impl.ClosedInterfaceEventHandler + + + + org.apollo.game.event.impl.EnteredAmountEvent + + org.apollo.game.event.handler.impl.EnteredAmountEventHandler + + + diff --git a/data/login.xml b/data/login.xml new file mode 100644 index 000000000..a5d9fb58c --- /dev/null +++ b/data/login.xml @@ -0,0 +1,4 @@ + + org.apollo.io.player.impl.BinaryPlayerLoader + org.apollo.io.player.impl.BinaryPlayerSaver + diff --git a/data/note-317.dat b/data/note-317.dat new file mode 100644 index 0000000000000000000000000000000000000000..8438dbb2980b396fa9929b0a7849acee170d54d6 GIT binary patch literal 10565 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 literal 0 HcmV?d00001 diff --git a/data/note-377.dat b/data/note-377.dat new file mode 100644 index 0000000000000000000000000000000000000000..488fec5b0486c42641ba86a1972b14927514054f GIT binary patch literal 12362 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 + + bank + 1 + Bank + Opens the bank interface when players select 'use-quickly' on a bank booth. + + Graham + + + + + + diff --git a/data/plugins/bootstrap.rb b/data/plugins/bootstrap.rb new file mode 100644 index 000000000..b5cab1c54 --- /dev/null +++ b/data/plugins/bootstrap.rb @@ -0,0 +1,167 @@ +# A script to 'bootstrap' all of the other plugins, wrapping Apollo's verbose +# Java-style API in a Ruby-style API. +# +# Written by Graham. + +# ********************************** WARNING ********************************** +# * If you do not really understand what this is for, do not edit it without * +# * creating a backup! Many plugins rely on the behaviour of this script, and * +# * will break if you mess it up. * +# * * +# * This is actually part of the core server and in an ideal world shouldn't * +# * be changed. * +# ***************************************************************************** + +require 'java' +java_import 'org.apollo.game.event.handler.EventHandler' +java_import 'org.apollo.game.command.PrivilegedCommandListener' +java_import 'org.apollo.game.model.Player' +java_import 'org.apollo.game.model.World' +java_import 'org.apollo.game.scheduling.ScheduledTask' + +# Alias the privilege levels. +RIGHTS_ADMIN = Player::PrivilegeLevel::ADMINISTRATOR +RIGHTS_MOD = Player::PrivilegeLevel::MODERATOR +RIGHTS_STANDARD = Player::PrivilegeLevel::STANDARD + +# Extends the (Ruby) String class with a method to convert a lower case, +# underscore delimited string to camel-case. +class String + def camelize + gsub(/(?:^|_)(.)/) { $1.upcase } + end +end + +# A CommandListener which executes a Proc object with two arguments: the player +# and the command. +class ProcCommandListener < PrivilegedCommandListener + def initialize(rights, block) + super rights + @block = block + end + + def executePrivileged(player, command) + @block.call player, command + end +end + +# An EventHandler which executes a Proc object with three arguments: the chain +# context, the player and the event. +class ProcEventHandler < EventHandler + def initialize(block) + super() # required (with brackets!), see http://jira.codehaus.org/browse/JRUBY-679 + @block = block + end + + def handle(ctx, player, event) + @block.call ctx, player, event + end +end + +# A ScheduledTask which executes a Proc object with one argument (itself). +class ProcScheduledTask < ScheduledTask + def initialize(delay, immediate, block) + super delay, immediate + @block = block + end + + def execute + @block.call self + end +end + +# Schedules a ScheduledTask. Can be used in two ways: passing an existing +# ScheduledTask object or passing a block along with one or two parameters: the +# delay (in pulses) and, optionally, the immediate flag. +# +# If the immediate flag is not given, it defaults to false. +# +# The ScheduledTask object is passed to the block so that methods such as +# setDelay and stop can be called. execute MUST NOT be called - if it is, the +# behaviour is undefined (and most likely it'll be bad). +def schedule(*args, &block) + if block_given? + if args.length == 1 or args.length == 2 + delay = args[0] + immediate = args.length == 2 ? args[1] : false + World.world.schedule ProcScheduledTask.new(delay, immediate, block) + else + raise "invalid combination of arguments" + end + elsif args.length == 1 + World.world.schedule args[0] + else + raise "invalid combination of arguments" + end +end + +# Defines some sort of action to take upon an event. The following 'kinds' of +# event are currently valid: +# +# * :command +# * :event +# * :button +# +# A command takes one or two arguments (the command name and optionally the +# minimum rights level to use it). The minimum rights level defaults to +# STANDARD. The block should have two arguments: player and command. +# +# An event takes no arguments. The block should have three arguments: the chain +# context, the player and the event object. +# +# A button takes one argument (the id). The block should have one argument: the +# player who clicked the button. +def on(kind, *args, &block) + case kind + when :command then on_command(args, block) + when :event then on_event(args, block) + when :button then on_button(args, block) + else raise "unknown event type" + end +end + +# Defines an action to be taken upon a button press. +def on_button(args, proc) + if args.length != 1 + raise "button must have one argument" + end + + id = args[0].to_i + + on :event, :button do |ctx, player, event| + if event.interface_id == id + proc.call player + end + end +end + +# Defines an action to be taken upon an event. +# The event can either be a symbol with the lower case, underscored class name +# or the class itself. +def on_event(args, proc) + if args.length != 1 + raise "event must have one argument" + end + + evt = args[0] + if evt.is_a? Symbol + class_name = evt.to_s.camelize.concat "Event" + evt = Java::JavaClass.for_name("org.apollo.game.event.impl.".concat class_name) + end + + $ctx.add_last_event_handler evt, ProcEventHandler.new(proc) +end + +# Defines an action to be taken upon a command. +def on_command(args, proc) + if args.length != 1 and args.length != 2 + raise "command event must have one or two arguments" + end + + rights = RIGHTS_STANDARD + if args.length == 2 + rights = args[1] + end + + $ctx.add_command_listener args[0].to_s, ProcCommandListener.new(rights, proc) +end diff --git a/data/plugins/cmd-bank/bank.rb b/data/plugins/cmd-bank/bank.rb new file mode 100644 index 000000000..406be8970 --- /dev/null +++ b/data/plugins/cmd-bank/bank.rb @@ -0,0 +1,6 @@ +require 'java' +java_import 'org.apollo.game.model.inter.bank.BankUtils' + +on :command, :bank, RIGHTS_ADMIN do |player, command| + BankUtils.open_bank player +end diff --git a/data/plugins/cmd-bank/plugin.xml b/data/plugins/cmd-bank/plugin.xml new file mode 100644 index 000000000..2191987de --- /dev/null +++ b/data/plugins/cmd-bank/plugin.xml @@ -0,0 +1,14 @@ + + + cmd-bank + 1 + Bank Command + Adds a ::bank command. + + Graham + + + + + + diff --git a/data/plugins/cmd-item/item.rb b/data/plugins/cmd-item/item.rb new file mode 100644 index 000000000..7752a8dec --- /dev/null +++ b/data/plugins/cmd-item/item.rb @@ -0,0 +1,27 @@ +on :command, :item, RIGHTS_ADMIN do |player, command| + args = command.arguments + if (1..2).include? args.length + id = args[0].to_i + amount = args.length == 2 ? args[1].to_i : 1 + + player.inventory.add id, amount + else + player.send_message "Syntax: ::item [id] [amount=1]" + end +end + +on :command, :destroy, RIGHTS_ADMIN do |player, command| + args = command.arguments + if (1..2).include? args.length + id = args[0].to_i + amount = args.length == 2 ? args[1].to_i : 1 + + player.inventory.remove id, amount + else + player.send_message "Syntax: ::destroy [id] [amount=1]" + end +end + +on :command, :empty, RIGHTS_ADMIN do |player, command| + player.inventory.clear +end diff --git a/data/plugins/cmd-item/plugin.xml b/data/plugins/cmd-item/plugin.xml new file mode 100644 index 000000000..5f9584375 --- /dev/null +++ b/data/plugins/cmd-item/plugin.xml @@ -0,0 +1,14 @@ + + + cmd-item + 1 + Item Commands + Adds ::item, ::destroy and ::empty commands. + + Graham + + + + + + diff --git a/data/plugins/cmd-skill/plugin.xml b/data/plugins/cmd-skill/plugin.xml new file mode 100644 index 000000000..f7c0d8b46 --- /dev/null +++ b/data/plugins/cmd-skill/plugin.xml @@ -0,0 +1,14 @@ + + + cmd-skill + 1 + Skill Commands + Adds a ::max command. + + Graham + + + + + + diff --git a/data/plugins/cmd-skill/skill.rb b/data/plugins/cmd-skill/skill.rb new file mode 100644 index 000000000..642bbf470 --- /dev/null +++ b/data/plugins/cmd-skill/skill.rb @@ -0,0 +1,9 @@ +require 'java' +java_import 'org.apollo.game.model.SkillSet' + +on :command, :max, RIGHTS_ADMIN do |player, command| + skills = player.skill_set + (0...skills.size).each do |skill| + skills.add_experience(skill, SkillSet::MAXIMUM_EXP) + end +end diff --git a/data/plugins/cmd-teleport/plugin.xml b/data/plugins/cmd-teleport/plugin.xml new file mode 100644 index 000000000..be9346a49 --- /dev/null +++ b/data/plugins/cmd-teleport/plugin.xml @@ -0,0 +1,14 @@ + + + cmd-teleport + 1 + Teleport Commands + Adds ::pos and ::tele commands. + + Graham + + + + + + diff --git a/data/plugins/cmd-teleport/teleport.rb b/data/plugins/cmd-teleport/teleport.rb new file mode 100644 index 000000000..d10f52e30 --- /dev/null +++ b/data/plugins/cmd-teleport/teleport.rb @@ -0,0 +1,19 @@ +require 'java' +java_import 'org.apollo.game.model.Position' + +on :command, :pos, RIGHTS_ADMIN do |player, command| + player.send_message "You are at: " + player.position.to_s +end + +on :command, :tele, RIGHTS_ADMIN do |player, command| + args = command.arguments + if (2..3).include? args.length + x = args[0].to_i + y = args[1].to_i + z = args.length == 3 ? args[2].to_i : 0 + + player.teleport Position.new(x, y, z) + else + player.send_message "Syntax: ::tele [x] [y] [z=0]" + end +end diff --git a/data/plugins/dummy/dummy.rb b/data/plugins/dummy/dummy.rb new file mode 100644 index 000000000..849708057 --- /dev/null +++ b/data/plugins/dummy/dummy.rb @@ -0,0 +1,55 @@ +require 'java' +java_import 'org.apollo.game.action.DistancedAction' +java_import 'org.apollo.game.model.Animation' +java_import 'org.apollo.game.model.Skill' + +# TODO: this shouldn't use the punch animation/delay, but should use the one +# from the active weapon/attack style (according to Scu11 anyway). + +DUMMY_ID = 823 +DUMMY_SIZE = 1 +PUNCH_ANIMATION = 422 +ANIMATION_PULSES = 0 # TODO: might be more if hitting it actually works instead of showing 'nothing more' msg? +LEVEL_THRESHOLD = 8 +EXP_PER_HIT = 5 + +class DummyAction < DistancedAction + attr_reader :position + + def initialize(character, position) + super ANIMATION_PULSES, true, character, position, DUMMY_SIZE + + @position = position + @started = false + end + + def executeAction + if not @started + @started = true + + character.send_message "You hit the dummy." + character.turn_to @position + character.play_animation Animation.new(PUNCH_ANIMATION) + else + skills = character.skill_set + + if skills.skill(Skill::ATTACK).maximum_level >= LEVEL_THRESHOLD then + character.send_message "There is nothing more you can learn from hitting a dummy." + else + skills.add_experience Skill::ATTACK, EXP_PER_HIT + end + + stop + end + end + + def equals(other) + return (get_class == other.get_class and @position == other.position) + end +end + +on :event, :object_action do |ctx, player, event| + if event.option == 2 and event.id == DUMMY_ID + player.start_action DummyAction.new(player, event.position) + end +end diff --git a/data/plugins/dummy/plugin.xml b/data/plugins/dummy/plugin.xml new file mode 100644 index 000000000..2bf4f8d31 --- /dev/null +++ b/data/plugins/dummy/plugin.xml @@ -0,0 +1,14 @@ + + + dummy + 1 + Dummies + Allows low-level players to train on dummies. + + Graham + + + + + + diff --git a/data/plugins/hello/hello.rb b/data/plugins/hello/hello.rb new file mode 100644 index 000000000..f114f9ccc --- /dev/null +++ b/data/plugins/hello/hello.rb @@ -0,0 +1,7 @@ +require 'java' +java_import 'org.apollo.game.model.Animation' + +on :command, :hello do |player, command| + player.play_animation Animation::WAVE + player.send_message "Hello, World!" +end diff --git a/data/plugins/hello/plugin.xml b/data/plugins/hello/plugin.xml new file mode 100644 index 000000000..00a3805c8 --- /dev/null +++ b/data/plugins/hello/plugin.xml @@ -0,0 +1,14 @@ + + + hello + 1 + Hello World Command + Adds a ::hello command which simply prints 'Hello, World!'. + + Graham + + + + + + diff --git a/data/plugins/logout/logout.rb b/data/plugins/logout/logout.rb new file mode 100644 index 000000000..388834686 --- /dev/null +++ b/data/plugins/logout/logout.rb @@ -0,0 +1,5 @@ +LOGOUT_BUTTON_ID = 2458 + +on :button, LOGOUT_BUTTON_ID do |player| + player.logout +end diff --git a/data/plugins/logout/plugin.xml b/data/plugins/logout/plugin.xml new file mode 100644 index 000000000..6246ec704 --- /dev/null +++ b/data/plugins/logout/plugin.xml @@ -0,0 +1,14 @@ + + + logout + 1 + Logout Button + Adds the logout button. + + Graham + + + + + + diff --git a/data/plugins/mining/gem.rb b/data/plugins/mining/gem.rb new file mode 100644 index 000000000..956b34dd0 --- /dev/null +++ b/data/plugins/mining/gem.rb @@ -0,0 +1,19 @@ +GEMS = {} + +class Gem + attr_reader :id, :chance + + def initialize(id, chance) + @id = id + @chance = chance + end +end + +def append_gem(gem) + GEMS[gem.id] = gem +end + +append_gem(Gem.new(1623, 0)) # uncut sapphire +append_gem(Gem.new(1605, 0)) # uncut emerald +append_gem(Gem.new(1619, 0)) # uncut ruby +append_gem(Gem.new(1617, 0)) # uncut diamond diff --git a/data/plugins/mining/mining.rb b/data/plugins/mining/mining.rb new file mode 100644 index 000000000..8b882da40 --- /dev/null +++ b/data/plugins/mining/mining.rb @@ -0,0 +1,163 @@ +require 'java' +java_import 'org.apollo.game.action.DistancedAction' +java_import 'org.apollo.game.model.EquipmentConstants' +java_import 'org.apollo.game.model.def.ItemDefinition' + +PROSPECT_PULSES = 3 +ORE_SIZE = 1 + +# TODO: finish implementing this +class MiningAction < DistancedAction + attr_reader :position, :ore, :counter, :started + + def initialize(character, position, ore) + super 0, true, character, position, ORE_SIZE + @position = position + @ore = ore + @started = false + @counter = 0 + end + + def find_pickaxe + PICKAXE_IDS.each do |id| + weapon = character.equipment.get EquipmentConstants::WEAPON + if weapon.id == id + return PICKAXES[id] + end + + if character.inventory.contains id + return PICKAXES[id] + end + end + + return nil + end + + # starts the mining animation, sets counters/flags and turns the character to + # the ore + def start_mine(pickaxe) + @started = true + character.send_message "You swing your pick at the rock." + character.turn_to @position + character.play_animation pickaxe.animation + @counter = pickaxe.pulses + end + + def executeAction + skills = character.skill_set + level = skills.get_skill(Skill::MINING).maximum_level # TODO: is using max level correct? + pickaxe = find_pickaxe + + # verify the player can mine with their pickaxe + if not (pickaxe != nil and level >= pickaxe.level) + character.send_message "You do not have a pickaxe for which you have the level to use." + stop + return + end + + # verify the player can mine the ore + if ore.level > level + character.send_message "You do not have the required level to mine this rock." + stop + return + end + + # check if we need to kick start things + if not @started + start_mine(pickaxe) + else + # count down and check if we can have a chance at some ore now + if @counter == 0 + # TODO: calculate the chance that the player can actually get the rock + + if character.inventory.add ore.id + ore_def = ItemDefinition.for_id @ore.id # TODO: split off into some method + name = ore_def.name.sub(/ ore$/, "").downcase + + character.send_message "You manage to mine some #{name}." + skills.add_experience Skill::MINING, ore.exp + # TODO: expire the rock + end + + stop + end + @counter -= 1 + end + + end + + def equals(other) + return (get_class == other.get_class and @position == other.position and @ore == other.ore) + end +end + +class ExpiredProspectingAction < DistancedAction + attr_reader :position + + def initialize(character, position) + super 0, true, character, position, ORE_SIZE + end + + def executeAction + character.send_message "There is currently no ore available in this rock." + stop + end + + def equals(other) + return (get_class == other.get_class and @position == other.position) + end + +end + +class ProspectingAction < DistancedAction + attr_reader :position, :ore + + def initialize(character, position, ore) + super PROSPECT_PULSES, true, character, position, ORE_SIZE + @position = position + @ore = ore + @started = false + end + + def executeAction + if not @started + @started = true + + character.send_message "You examine the rock for ores..." + character.turn_to @position + else + ore_def = ItemDefinition.for_id @ore.id + name = ore_def.name.sub(/ ore$/, "").downcase + + character.send_message "This rock contains #{name}." + + stop + end + end + + def equals(other) + return (get_class == other.get_class and @position == other.position and @ore == other.ore) + end +end + +on :event, :object_action do |ctx, player, event| + if event.option == 1 + ore = ORES[event.id] + if ore != nil + player.startAction MiningAction.new(player, event.position, ore) + end + end +end + +on :event, :object_action do |ctx, player, event| + if event.option == 2 + ore = ORES[event.id] + if ore != nil + player.startAction ProspectingAction.new(player, event.position, ore) + else + if EXPIRED_ORES[event.id] != nil + player.startAction ExpiredProspectingAction.new(player, event.position) + end + end + end +end diff --git a/data/plugins/mining/ore.rb b/data/plugins/mining/ore.rb new file mode 100644 index 000000000..42f3ef805 --- /dev/null +++ b/data/plugins/mining/ore.rb @@ -0,0 +1,96 @@ +# Thanks to Mikey` for helping +# to find some of the item/object IDs, minimum levels and experiences. +# +# Thanks to Clifton for helping +# to find some of the expired object IDs. + +ORES = {} +EXPIRED_ORES = {} + +class Ore + attr_reader :id, :objects, :level, :exp, :respawn + + def initialize(id, objects, level, exp, respawn) + @id = id + @objects = objects + @level = level + @exp = exp + @respawn = respawn + end +end + +def append_ore(ore) + ore.objects.each do |obj, expired_obj| + ORES[obj] = ore + EXPIRED_ORES[expired_obj] = true + end +end + +CLAY_OBJECTS = { + 2180 => 450 , 2109 => 451 , 14904 => 14896, 14905 => 14897 +} + +COPPER_OBJECTS = { + 11960 => 11555, 11961 => 11556, 11962 => 11557, 11936 => 11552, + 11937 => 11553, 11938 => 11554, 2090 => 450 , 2091 => 451 , + 14906 => 14898, 14907 => 14899, 14856 => 14832, 14857 => 14833, + 14858 => 14834 +} + +TIN_OBJECTS = { + 11597 => 11555, 11958 => 11556, 11959 => 11557, 11933 => 11552, + 11934 => 11553, 11935 => 11554, 2094 => 450 , 2095 => 451 , + 14092 => 14894, 14903 => 14895 +} + +IRON_OBJECTS = { + 11954 => 11555, 11955 => 11556, 11956 => 11557, 2092 => 450 , + 2093 => 451 , 14900 => 14892, 14901 => 14893, 14913 => 14915, + 14914 => 14916 +} + +COAL_OBJECTS = { + 11963 => 11555, 11964 => 11556, 11965 => 11557, 11930 => 11552, + 11931 => 11553, 11932 => 11554, 2096 => 450 , 2097 => 451 , + 14850 => 14832, 14851 => 14833, 14852 => 14834 +} + +SILVER_OBJECTS = { + 11948 => 11555, 11949 => 11556, 11950 => 11557, 2100 => 450 , + 2101 => 451 +} + +GOLD_OBJECTS = { + 11951 => 11555, 11952 => 11556, 11953 => 11557, 2098 => 450 , + 2099 => 451 +} + +MITHRIL_OBJECTS = { + 11945 => 11555, 11946 => 11556, 11947 => 11557, 11942 => 11552, + 11943 => 11553, 11944 => 11554, 2102 => 450 , 2103 => 451 , + 14853 => 14832, 14854 => 14833, 14855 => 14834 +} + +ADAMANT_OBJECTS = { + 11939 => 11552, 11940 => 11553, 11941 => 11554, 2104 => 450 , + 2105 => 451 , 14862 => 14832, 14863 => 14833, 14864 => 14834 +} + +RUNITE_OBJECTS = { + 2106 => 450 , 2107 => 451 , 14859 => 14832, 14860 => 14833, + 14861 => 14834 +} + +append_ore Ore.new(434, CLAY_OBJECTS, 1, 5, 3 ) # clay +append_ore Ore.new(436, COPPER_OBJECTS, 1, 17.5, 6 ) # copper ore +append_ore Ore.new(438, TIN_OBJECTS, 1, 17.5, 6 ) # tin ore +append_ore Ore.new(440, IRON_OBJECTS, 15, 35, 16 ) # iron ore +append_ore Ore.new(453, COAL_OBJECTS, 30, 50, 100 ) # coal +append_ore Ore.new(444, GOLD_OBJECTS, 40, 65, 200 ) # gold ore +append_ore Ore.new(442, SILVER_OBJECTS, 20, 40, 200 ) # silver ore +append_ore Ore.new(447, MITHRIL_OBJECTS, 55, 80, 400 ) # mithril ore +append_ore Ore.new(449, ADAMANT_OBJECTS, 70, 95, 800 ) # adamant ore +append_ore Ore.new(451, RUNITE_OBJECTS, 85, 125, 2500) # runite ore + +# TODO: rune essence object id = 2491 +# level 1, exp 5, rune ess = 1436, pure ess = 7936 diff --git a/data/plugins/mining/pickaxe.rb b/data/plugins/mining/pickaxe.rb new file mode 100644 index 000000000..b7acefef6 --- /dev/null +++ b/data/plugins/mining/pickaxe.rb @@ -0,0 +1,31 @@ +require 'java' +java_import 'org.apollo.game.model.Animation' + +PICKAXES = {} +PICKAXE_IDS = [] + +class Pickaxe + attr_reader :id, :level, :animation, :pulses + + def initialize(id, level, animation, pulses) + @id = id + @level = level + @animation = Animation.new(animation) + @pulses = pulses + end +end + +def append_pickaxe(pickaxe) + PICKAXES[pickaxe.id] = pickaxe + PICKAXE_IDS << pickaxe.id # tacky way of keeping things in order +end + +# NOTE: ADD LOWER LEVEL PICKAXES FIRST +append_pickaxe(Pickaxe.new(1265, 1, 625, 8)) # bronze pickaxe +append_pickaxe(Pickaxe.new(1267, 1, 626, 7)) # iron pickaxe +append_pickaxe(Pickaxe.new(1269, 1, 627, 6)) # steel pickaxe +append_pickaxe(Pickaxe.new(1273, 21, 629, 5)) # mithril pickaxe +append_pickaxe(Pickaxe.new(1271, 31, 628, 4)) # adamant pickaxe +append_pickaxe(Pickaxe.new(1275, 41, 624, 3)) # rune pickaxe + +PICKAXE_IDS.reverse! diff --git a/data/plugins/mining/plugin.xml b/data/plugins/mining/plugin.xml new file mode 100644 index 000000000..f6f6fe75f --- /dev/null +++ b/data/plugins/mining/plugin.xml @@ -0,0 +1,22 @@ + + + mining + 1 + Mining + Adds the mining skill. + + Graham + Mikey` + WH:II:DOW + Requa + Clifton + + + + + + + + + + diff --git a/data/plugins/mining/respawn.rb b/data/plugins/mining/respawn.rb new file mode 100644 index 000000000..34d0d8b40 --- /dev/null +++ b/data/plugins/mining/respawn.rb @@ -0,0 +1,18 @@ +MAX_PLAYERS = 2000 # TODO: obtain from a Java class + +# Calculates the number of pulses it takes for an ore to respawn based on the +# number of players currently online. +# +# The 'base' argument is the number of pulses it takes with no players online. +# The 'players' argument is the number of players currently logged into the +# current world. +# +# The base times can be found on this website: +# http://runescape.salmoneus.net/mining.html#respawn +# +# These must be converted to pulses (seconds * 10 / 6) to work with this +# function. The rest of the mining plugin rounds the base respawn times in +# pulses down where appropriate. +def respawn_pulses(base, players) + base - players * base / (MAX_PLAYERS * 2) +end diff --git a/data/services.xml b/data/services.xml new file mode 100644 index 000000000..93b551fcd --- /dev/null +++ b/data/services.xml @@ -0,0 +1,5 @@ + + org.apollo.game.GameService + org.apollo.login.LoginService + org.apollo.update.UpdateService + diff --git a/data/synchronizer.xml b/data/synchronizer.xml new file mode 100644 index 000000000..635b2bcda --- /dev/null +++ b/data/synchronizer.xml @@ -0,0 +1,3 @@ + + org.apollo.game.sync.ParallelClientSynchronizer + diff --git a/etc/jsr166.jar b/etc/jsr166.jar new file mode 100644 index 0000000000000000000000000000000000000000..6ebe070d4cc65a61c023cb2215b610a957ed0f14 GIT binary patch literal 544543 zcmagFbCBi1lE>S&ZQHhObK178Y1_8DPrIjW+qTW=p0@Gk?%mxNci-N9C!*?i{>c1A z)rmZx_-1C6k}N0~4A4J5$ebPm|MBHNCukrrAbD{$VR~r=3C6DpARwjxG=%~}`fCag zWzDMd*VO#)K>IuX+f-gyL0UpwRgFPj;$D7gQcjkhVIE$Vo@Qohwo!#?iFNPTc}f(; ziB3vxMoJwh3YdKQLC&i^Ct3wnRz=k{mnQIn{t?bK8IBH}ElBnr&6hPf`YxI_T2{p+ z8lCSx>5;34$)*u~Y^$i!9D!PeHy#MR2dp25V{$i<~u(@suj75itL-!!|SzyTN(80ag>2~Ie> zRdU#`5Tu1fkZ~CKER`%QlL%Ck&Xc6DTDt}I%?(?-=j{O=>dmHl!gYz}RI+J_R%z8z z>&qTB`D_ZiWWeWIlk_`oqUUV;^P6#y@J77!OP1KNNn z7w~dXIe`&aWJwW$BC;p>&&M73Urx#X}Kbz)egP!s)OpPtC!hJ)ceh3SZg5 zD~P9)0)b;aQZ_RKr+!jA=FufNr$FJsFo>FTwtCy|5z6Bi>erX1sC2b1x`b7nSjk6s z;IiKRwt$69tVpPo2eHG%p7Gm#DQhA&yyBtFWTU7=d@k*=0$Ype?oSjcx^1rMa8i%KS+%=aFcNcbukn z=PgG|9cwi`yeZkIzJr152u5t|e@4PpIRoIfdGvN5-kqu#wkr zz@6~uioKEo39GD}F5SGlL}+r;iT!|pgy5>N!MD_}Sh!BFf(OXVQ>mICObLw)ujqz3 z_s`hzF%o!nu%?)T4cuubMq|rE*#E()t!Ws)Tf@e|CYHf=bCyf~iNNCSRWasyz;_&) z$>lL*91|ya*;Or7+A>jJRbIIfsn`AbB{nVMoh98`JCf; zx!nW2I;B?cSj|O`AjbiOeda`0g#Dz}kq*Teru|W@H!o^rG^0NAf?12BlqH)o%+6Aq z7Lu~KK2a*l>b+=QMI{HywZ;7yW;x5Mq2U_#4K=yK@yJn4!+3b&t4`Ja$q=^1doCp2 z6G@14ax{3>FdZ^~n3kX`3KC4_4|~-CJEB1NtdAoWqSp<$ukvs@-%A5t??L~ir)U9< z&0@(<*QRtZ0H8yKdOgfHUMw#w20%jUMyw6$)=;{19x0|rmx2>5l@0)Rr^~&u^fhM5 zlq-1{8=EhYQ}o_-BgT`Sh=d#^3wpl|faG5=7~Mm^CP$B(6B?f0hFe4{mypEK#-{!19f7zM{LzFP?WAT;*9TIj+jN+K-n{>?0f!=pg%KL`d3C5|S8drO z+H8;TMW?@{ZTp38hmq5JXBv8FB)t}2_=HuLbJf!~z*)B*!W{e&@CRbc1vozv@ZF#~ z1^CVZ*2c2z3iM^c&uE;t3(THPfAP(X(HF+n(SzsR=I9y#(l1o}g+0c0k*>56_X#mF zh#|pA4ml^x7#;CPKPH?$=Hv&23AV0>a#2wP({R`(6$5`CCg`{mBHfjhXHaUCw1Q(~j-DL*-aFhp-sCYP*gc1fCjot%!W`!fl6^yOPq4rT z6lJ?R3@n89)q|diid2IxN@y+;k4^cYR0LR+vEaPDM6Yl`GEX-|V3cryd5BIc6q^SD z%Y?=SVZ&o~1*LRNpbs+n0aj)gE$SFiE6*PenQn+qb9Q&;eos+tI)@|bgQtp|ZMt3;;`T9GKBSX9fu1b>ssrvdC;trNKWh#7nkd`2Ohw-_(K940 zvZn-aGZ+m?IwNBd>+lwB?c4o?0Gx9dt

ku+fHu`1@=e0UtV^pq5 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 z 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

gF|wwG za;t*xngZ>?8kb%8-CT$eCLB1qcE+BZiMZ0vQx<`~Nt4iJYs>|~= zbpYPVTatFUp`q@1e761k8!_A){y<%lo?9X*9thNRsFjpS$S9-t7NWszb~OBOd6p$S zCNi1{^B0^nBwCOO3Ne-CYQ&Tr>cTd^7v@F~n$sB1tT7X_oL@vt+4`0LcNxbNCT)$o}Cr#DR{S10=QhG1nOC3G<>OnNVgvU*t_67DUl4fES zgJ}8;NzqyH?`6(=fqY9$`~dFWEh|mE_!Nt8WLEaZXiLNmaRmh#%Q`h^&&NJEl7gGG zzX}AxhaNW!^L4wC$3E7^Uo6J#WRf(}&p_5ORlEzonD#j3vqH zwQ|?XDvc>omO+F-A$p>`KO;X1&dXA}JQn_J&1ojSve|FK~2+~{w=bSCwZ%G3n6olZpk|nZDGiZGg@A@jC=HH`Ej`LH2#RGPGSC?v z@9cm~8&TZ9dF!As{3fCq!T?~vvCSqBuuv#8Vi=Qzr@}N97dor`UH>>xg5K?sWp5&n z5DHnUsh^AEyL_KrSoHsMK~d~Sn{Z`6-#S&J7xIxH|0^3~>AAST*ei^qBuhIZAIk2;i=dpf@IG`S%S zguA~?XwV2TAPd^s>#L8MSAqIrBnYY?y=Z-dHDtSy`gNM1>bTG=hQ(3bRo){*(M<}E zDr2gZGe4zhRnLfx?(0%i)A-~67w1Ath!ycW3Q?H}Anp>~KLegyc z`u_7#ZbfS%^_VsiabG*u-(3YgPEQXpaWulg=|7U z6A)`d$h;lN;01*ElxMU1mGrNwIVn;T+Kn2$IZ3NZ6Ng=HHQe_%cxn3Im9wiRqy$B& z&DqVfBOU4Tn!oMOw!fYX*4}@wy%P%qxEwgvgOQf}9jx7kd|@s=Q=ZoBEQWqgCJo$E z5LL`s;OXp7Y~GpnWoug{|GETfUXHQd$&i1t>&`3gv7>r~%yw=#iZagPLbCfxC@+bm zTEH&XEGbT=b4ue7w}&WZiwUJi;2}V$Vr$CT>--`hm1S8{ zU58CH!Sg4AipTQHcX7n%i>J>Y==7)OtM}M1$t9bair8bD*d0ZA7RV?C?K0qK4xq^E zm&e(#GlF9L=7@D|&-^{ZFE7gx)hBDbN-xyGlU4MAc_9I7_&!1{XF#_eTe-^An!*#A zd;x@;4B^1V6;yqJ;o7+Go{-%Gc;ePIBXDzi--&M0Y>#BmRW_z4>=OCbbfniW0Z7sqxsaW2U^e@EDTS5XvfIm&7=V5}|pJCreWt*w6)^zqI>$ zV}_!6#H!?wVB1Qpn!pw`9>u#pak&wKHzEGAmOWvOPmK>MXai!?uf1(zby$oK zGw*Zc6SCz~wH=CRyv0X2@|7Bs$Y;d<4ntPgZNT`>H#Rn9K=~dfe#5O=A3HnH9!GSf zISke{!LredyE5WpeMHASwYf6P!#?njeawe_^nDj~3%~YWyK@v`$A=fe?3Dl<&o}_y z08h6E{5Id31b=Ag4$=BocaqFOYP}_|V($&v2)yp%N?Akmq;ov8m0baT@yGsd35`8u z_5)RFVdA?jXL7#f5=)Y$9+MJmMf$S5Mu%9h%98QgEH=BwLJ9DK1$W}~ zzbZ@bi^yEasdE?n0FSbTb5A$?_Ki>y~UR2(~ z>IGfjX=d%XxQEjd>gx&hbjAGn7xm}UfO}tMJS;V|bl6)TZqr23$dz_Gt&B>EpOSk&DHP`C# znG^m=;;<234tGEP^>9MRFzfigSlt@ssfChOhlrPHG)h{!N2n&e3MnSKT_8y=ZLqY& zjU)HsfMUxWur|bhEpdb5Va@UJ*)&Ob{E3i*%spZt4JWRI&xaSrw*6+t&6ry{I&OYv z?WZarMAO8`@VUF|5egQxaQLSr_>U-6?cOKA{pjgfGp_T$+#i&xgms=NRE$IODQCe< zG(?wFQPz*f7)8t^X6at@;zd(lUBo5RmZknR8DgwoT{3#FzR{g%k~13^yDYD_xL}63 zafP*ZUg~bWjt<4`E^;c@_TS)vrV0*eoz4cgoUF%Zry;I{R25=$F}n>IXnPXzc%6f_ z8MH7<|Lu{kDhl_9OUyNRwGo!o>xJ}%5WVGc0NW0ue$`3qyA}o84g&LLO1Ys`AH5a< z>BhHuS)Luk@3Hw1APTK^KC>xQx;EGtcH05|qU9d#jz)JUN9-$f59JGC`fwl${`Ugr zhvawgeq;89@->u8marm6a!WJ5C$?Jr!9f&aRF#Eqrt$%*HIi$#e1pE?=$jDy`m7$* zJ)-zQ+T!0m(KVrG@O-PbD)hn95}Ir1^?H7R`33TX>zjdlY*&Z=qIDtj#rpoIyWe+W z+r;eE>w?%fo7X4oL&iHGsd%$?h<9^ddWZJaHx@lRb{A^u2()iiu@$(iS*9oCIdjFOyE0o>k$5PTI;K@fyE8aUaZTdFGFx(s*^mm-!X$fsN%5!D|Mblw2xr$N$B8W65jnCVBxK6L{%L^fLYr5m)7Imp7BHalB<@hoaxd38F zU%>}!d^3?+UI{Mr#;c%OR8n7RL@Q1x1^5QcCa}n_g8FSUGXyx|P~@7y zFl;bvwy4hc$hzDl-YcXHGlUJdWh1@30EG8oe-NTSN$D0*^*3BQO!_KW~g z0#W*_dy1_5+C$*s@m2gN^#()(qLGTMN>OG~gP>1f4q%ZHj7t<`4cG(87p#ErO+vE9 z=z=U8R3(kjGW~6&`4xb|sBFaP1u7u$AE9XjlOfuBb!&tVg=r&S7}eB(_7&27tD5q? zuOTmdIGA@-*>15RVYi_6WGzbM3y#7tde~s6F3oP`086KW_BdNuv3Fr>j2+VW3)=wt z%f2BPAKBR}{D5Sp{d1#hq&<9{mQ5dz4f2&mroG&Hkk=a7#ysopRWqa;^R^A5Ef}r| z+3r-4&=kprrEiBwYwTR}ILU^Ow>ka)dy{UYy-Ztz{bagD-)DtHtJE9O#w-t%+lM^S zMl8epH)1i?x8xY@S!!aYOv)=+eB3MY&ag4=tAIN|A^p6y8^q~TDV)L9vT!b6QYU$p z?mk{7)^+OF`tF_FWs!QEvA6cRJ!q;7Tv~S*9V9A#kkpvhT9>1AN+(^$4jPoZ2$SOg zT&pk%A_^@Ap&|yUrUFq{m}32J)7Qdsd3@Qq zZB_JnT0ie0xrC!KiKnutX%7^a39pzu_wyz$%sFYqm6k^JT zNJo0Uptx@Y(ZiS$Nq*Icaz{wg1niIXU9Jd#)MqH*NtnPd_7yG84&7S@J#!a9OMLtN zrvM_}xbp)bWk~R*-b^`>zaQH)mt3`SDUS_fKjAik752szyQfRKb9D2;f!jdhoFMmG zG?-s$z%SJ-$Kco()1XoyWp@gG&qHsEmU{BOegC(ME|w6IQT7LOnf;S;$n#$syRxIn z{}3)o1pZH!=a2X2zph1WogF=XfQ70SCT{;x;X z>LTRBK@k>22lLrreQ?kwGM3nmUZM4ASrjCg?}5C44ut}8G&JX|Bu7k5r?NPX-@BO1 z8h*@@FJLaSgI~3>PquQxOgPYBrHW3A5-B?`{!6Wj6rUf84R(-8uZN- z5Fbgs6lo=OPgol*!IO2GA0yN5Er%nbE!I#1fZMJt9D$=^%52<_<^j09L{#u=-FtNe zKZT6Fy2o;=D#lvY5hf+;CHKNU+VUrxQZ)(Fv<^{EBhDoe33gPV}VIDvOnRyTbxKL2l-zK%3QRO#mbt<3maQdc4{R~q+u-AJ7BM=2%qr(JyCJ| z@**W6fPhYZ%pm0de`et&WBho@ z;`hn**@=s)I4U~Jw@2sh<Y9PDeX-145O)Lv<=0J#B`HwU4K_pgJk^uS=vv+W+cl78o(@KCCvv;6g z*Wke{?Ar|_{Yy*8wQ(KDw#n^t8u!qF1KIZSO;A8gU~<7Jji#(cNxS`qx9mlmtcT}JM+Ny~21)7D@6AsWxuSCZAM zfPfSzw^@>d`4pp^GCKOSLk*)|GKu9TEfQhy-2yjC>)R9>XrsKX(<${6kBA?Zhubf1 zu+w=HH;*BvF}%qPwHf1%i@M+W0j%rU&JnK7hSdr0Nym7uXck`yA{C_6MQ71{I$Grd zcxyQ4Cl|`TEphnde(Psup-qzmNN<>x2N=4|(n}D?clb1ES@0$WhRS%MIpe)D z!IRAy4TBUTqOfsc{Rh4ah(Pz$0{!siW+Gnmmr!6RS}jbS0oL39UK2FYLWreCyF*G( zZlEY%V?glX{(;8?MmciMDdWyxaWw8(yeM$Hu`n27%HWw-!PzKo`rUOMD=%yO53(B6(RlTNdF75(=>TK6>>LPPZ;P&T<<% z#GgBUF=)*=U_YI-BD-0<)W<;K%-te?B*}i5q$al5q`gqA> zef#I^eZJ>dYc9=c(J@YzTLLZMoE0Y4Kb`4nhkMhCTR6oVC<`7T`a(BQ&^J3Fv)hm*K@+Z9kh4Ldq7qGR0|pf(D9dBT&3F8F-> zC7amBd^Z?W-&1sGqL83(;OIXtT19d#4a3jcbOcuR>t%y0B1HX6nkfGkRcALZ%pCrl z#hF-RM)F(ZkxWnEfpP&^ZOYwbLL$fR#`*~Aj@xKMqwJ+R#4^a8Zkf15ndJSM+#7w! zXo}q$1s3&yTgVg1cGDYTq0*LXf2NxnQsjfX+LYq+9 zTW+QU_|Sjbt7!X^gP{62T-iZk=$>vtsWLGhr+__YJHrcBHg(RMFigOt{jE!&_T7o( ztv)R1^DPBF@e<-XIb}Cbhv^z|VxF{#C*}#Z=ugLzi+S&i;(}0vT8a$;?nl(X#qnwO$|GBayADKI+=_eK$xtZolG0B2k-U`Z^*`BH$}&jAnF> zp0s)EZUWfxm@&T7!auLPQ(MeEtBKq`H|mx#B90bDoH$BESRR{~i}wmelceIpz4;9~ z;ye{)`GN=wf6*Rcx=x@5;(D7XgE2mqjMIuIZFsD7_?nWn?S^oEe0r~<{%Fx2mlyZJ znXK;SVeTd$mNzng3#{WD2|IX;_uI*s ze(;I<4ql@CH%goj!#l|lN_43L!K@MDwtJpeEl5S-8Nd0e{qJzRaQB!~o*i;T{8CgL zPgh{=KaTCvr}?FBxT~X)=hF&`CKnM@*pwB|$e#iUk$n4x)V%CfM%r7$xwYfL0q_UH z3&X<3p^bCW>sxx0UYlU9Q^zX~Mm)8@^Si!SlU8>KYBjwvBICQBz&uQ{F()t1yw2jo zXbQ}JNsEA+#7a3x9r}OKqL^3E&8d*gnH0zstXDfU{_YiPq*E(e2a3k2&cN;n$~|bC8RI-?L%C1OB$V6ucC{cSUH8w4M3s$*!eUFQ5}&vw@T|l#nr^nLt0^MTgZZ zbBMO^JNeJ^{EMvf*T1A=Lp_>kH~?=n0IZ4svk&}#^TvOez)PEW{HqVVjUAF8f^UnP zeQ*w~PvOcl;O4AMLH0vrk-nNt_KuWt{~btsD*}97JM06Q^$SAQTk>NM9^iFq7F!6$ z8P&cueL2PRcWUZ-XI6jr_Ye0lM<~cGS>o_37K?V2g|?GltNx>LOY|mjn`Ro-dbdC6 zpPMg}tfaz9yVMDv{U3_?oa4+Sc%GWEnA*sD=$8~j!TljHCiGw&H_LJFjrzl=TLm5->#GydFF*2wpC$XF)G#+ z&dD6;RP!*|%u+_4=&IQRBMVDkqj5j7;m0*)P@nGGf(#Z z*#F^k7qX%8x8M)=mR)KtJDrGfCS5lVXySWt0&tCJ*f(>X`1dI>oIzH}%!6iYZN@=3 z%b-26@%WMB{;wTTD0Lka1?T$EcQ`4S&hK_s81BXRyJfqnH2CTI(jkI{S}%uD!>-{K zYDSh&T2t3aWs1wiY{>;t!}O@L#3oM9a?OOuqUK-oz-_??=ygI4H`ssp<~IhAe9)t3 zeItWD;h>+qjK@-7ncFl*s1PizG|07G2U;D@I;C2sE-eBGj zNv#&vuHXqXxg)~Y8j#Tj87I6G`P|1Hn^2eAI%isT=E{JWCG>OXCDQ~6qAL7~k1fvB2=@U?FJp=@r2 z5lPj0nk*g;TVy8EI!HUUA-<)MlSPn!`|(e>n^`o=Z>CLOZnZnzaGL$unF0XK7XY+v z0+uZ}LO)hmHz5cE-A^{5TY_@JVnW$lZt?{=Yl}AduuJGpvMD2r73u18?8Bry{EU)1 zDA>W4U|9*2U2L-v)5-Yg=>}4eQIfYrN0^M%66Yrf>~TpXCDee{IUcm2^#Yb*#S39o z4U@u+U$frzn#@?Jg9(<~uAa%3LJ#yy(2MnSQUm1@4sv6%0+|>&Q}>14N$91x8v`J9 zulE-^?FpNcF+uPW;u;}~O=*AuXMQyM__Kw zx8e}V8MKvT0Hycfos=9^O|F2n6-HoNfAFA_KyV(W8Drh--+LQ1ICPu)xx~{?!^mpi z>G@8jd@J?UzRd1IYp*v8hz*Cw&t#|D(y;Q7cXtn1If!T>Tbu?y8iUW0_U}G6))?y3 zWgE4|pCW59A-DsM@{imWbj+(9Q}e@rUi}%YbdFjRAuz+)9EBxwvDZjsp0~gZ96)}w zyvRb`{uPN2JLhRBai98y_VHEsn6Ns5EN9l^w~#I^<8x39?)35OgwTZYXMH6(ngPu| z`@}rfOcC)HBs+$bzuKPDv*GT(N9xy98&A)G7waqC{`cBw{MkVmto_gi4x{=PRcvOB zOXRb^E}tR9v&MLWm97!&;LEGflk6BFm+pte+jW^hT#wr3oLsA-` zCIJTApHciDVfDX@l6Q6NQPeNAIJ9t5jbA9(yMm(fq*gQaP>%7a5!O5*@bsN)KLG_y(tEMEKXv z?MBd5yyL44Us5%^(}&eHypxAlHN3Nj*;TxmW3t_~hMjnhxoLW^#gS$!-Tkq5jQ~nP zy6!pTT~!C~4mf@AYahaB{DyZ%X#9$I%Ank0(Ml_IXtLDox_W5~si@|MDJE;_U3yfQ zq@d%`h$l3odf9AQnu~36%F-b==dYTBbm^iO_b+8Y$6Wy{GV@fN9miAWuMLRvdOkJw zc~>T~f&*-7=2h_k3mzA3w+J@Q+^&k+)6t2Tbp93td=-RDHm8*9Bpc!WojE~aaWgWo zPz!VCu_hTajeh$HX8RE~7%4OR9>d8Cwd=Lv;7YL-%q;ju+;r8sBuYA^t`^BfCb}eO z=n&pQ`lIm$KFukm60T!u8w+d46q$q4QZ{q>I2ECC6cp#6y+W1Rwc&xL!cDknKmij+ z?>K}N2BmdB>_DAKpE`vaf$tD;QYDJjYpk@IaCRp3c2`5E&bZqPTPHkpF;$i4Dt~1! zZ%14Xr9@cO8Tdys9X;SR+KgAam)fOh=isJxvAO<;v|SxvE||t-?cDQ2cTRl@XHhX< z-{g%vN_C^X>J<|032ju`i<(yT32QnzsYE#|N1|R10p8?1A)g-VNPIBP4v5**R)8aE z!DolO$zmf6e?{D0_;zUY4q5q>48iTl?W?fTQWGEQz3VAd<>Hqng^_`q5ln@x2RHQr z#)KUjEsY~-L{rE|QftZ2BM&8XHWBjw%dFP+hAoi|ywetq|rQVS&E>&q`+lssw=4q_}Y z1Q@{HnApS(YufgNHuD26oSv87Fd=;*nwB+YY<=JY%)P-KXUV#;cKQ;rY#ZDWT2XLz zxilogM8XbPpFA>$@de$E)2lfzA-_)zjO{En#5D(VH9pdZwJAc8umq%!X}fT!PCFal z3Jw5{T@aMOP}en1MPX?fcpHbFY*tB9yrb{$s1G4Ne9PU1+}*AlyP7+5D)Owa$!_y{ zM~myJP;LsVwU%0`RNB0lp27>@N~Ntv8}%0PV6T6P_B#)$in(j(lePwygu#0@^Y)LCO`y~vM1&GSX5XZ_g3_NO61`P--`S91xm|x4)_|JA~j!sq%G*~sy z%^z*x9~RNSeI7Q8{C%--1hJa73S9U%+D#QZ^_%boGG9QQvL29&x&uw$fS*dep7E}% z&!aA5Tq~>;7S7n`?6IR;=be$`S1m+{@rXRoyAeORHwwOodQSQ66RcCI@a&;Vf95Nm z6@^tP+7w7sDf%C@7bzwSCC-(_5fZUfn1(?`OJRN5PsHV<0K_Mj9=+t=cq19lY=WA~ zrmhjseNYaWS`jVpP9ctwJf>v{Cq_DU>SdU`cZTS zpGKPNhH|a$bcL*KS{%$7MK;>t(mG9<5t-ka$IWp);bMDxh8}W7})k zpsdNM6tp$L{fdxK3UU=(;PQ)1Sozr{KR3bYEG?bPk;6%5riU`bONPMW>^ufTuXw{^ zY8*nZ5OSG6o14#{xzqh%S$sn0x#6uDE?Y1{QORMlOw>^R>)fgF40#MSUl(oN7z7{I zajA?uBA%a2O}=~}n{@HLHDL2HSb7%D6X~G|96cY1UC@3L(pDU|O?fkbTNVELFfGwu zUr0xWTT+9I%xl+w;~mO#@nTIgaB0^5W-W;;+FIeO-IM(+~sfbjIoo?r}w~ zhktvU;p`#X=XZ9o8MB-?;;?kYcbCG2*( zBB9zgxD+{zp${Rgw=C@8TE3O1T^1iluv% zS75zdFLXU07G5Pe#>Lb(Zl$6ne&P1ajr<-%_iW&|raN$G9W=Ovv;nve%~oG>iRqDq zc7YdLthz$jNS(|RQ1qtcjom!ArT_mf zasHET{#Qy*R^3(xT*7|Su3v5kfokzhh+FufSu_ZgN9dChpyi9^H3ul(IzevMU1bzy z%8tg^#oU}bFO@T&2g3W*!E+JsjYbv9&%Cg^;LNUf29bbN!gG4fwx8s@bNBw;oYw#S zeUI%&UWY19PB%Q~KWUA&-Zf+zKFySg>R&lMcjt(+dJ9#n_Bxl*SLx8Shm;u=v-(7oNV!Ppn(nP;oXRD1|lRPV{v)yH2_DKm^uS%ZU`q5yfys?2KTF^?{isO!X zud>>6t!&SC_kLMx<|#Gpd&an89;9GgrLR-Y z15=VWG|jtP1u~jtFVBFpd(Sr5?14lzKAGiRZQZfFv_{M&%(_^BpKDQdS^1I03wwC6 zWL()=^RfC;HUjJ+PU`I3rm~5aGnPa-5(-4}mTCAD?cUITq^R~iSasLwzTGp#IO)8$ zG?flVEZYJh^Hp7wMd_D!v%*uuQ^24T>i}iWAv0LWU5S691O|q&6P6i_^L~)O9-FWP zL|1WMVLvF?2m{@1i@)1#jXz`*b{d#uduLB~q{--Z11~TdTX?30o-v^RwdH$n;6cfu zwreWD99~@v78P(9DkX7$BFvffDPM~QEz%ev%H3Q1>jP1$>ddNwjz8;c{*^;sze6sL zCT~78{@8Mo}uDO|yyH8>V0Z}0?13n+C6X8zXshrg7sJZW!j0>8j(L$%@tX>;&&mjIrb_%uG^XAhA2 zVcrXYpYd1_LV_mpzO>1@6H2DJhFT^4`}{YBSB_!q;&(R9J0CA`*0_m!nzhYko+tH9 zcq}4nE&&}MtSx5JD=J^K0+J%udE(DpQ`r>((#qO|VHzoxL=-P5`9u?z-C%Pv@o&E` z(t>#K!d_v01F}(HF`0h(uw7wxBhp^ZhhR^9%cGy!%D_ukJc4(aK`&WJcc7GqdZ_o~ zQ1_BN$OLx9ow>tKgpTE5;|T3rodWfg_Zf)cydp{jypxN0@KO*Twh&v!Wxs_)u8C(R zbulc&ybu;BSbQ432Z7rXj%p>k^o>Nkl|h$b@?8-$XN}m}q)ADqLKKeOrx7YZ%Gl^B z;KMUO!6fkO5%$1v_m75Z%{6z0D?wIQe67((Nh?}(n^rH*H>q?<;m)WK^}4?Cy24)f z+WnOKQ8FN2SRWQQ3lE{*n%DltIPCsNj}i+|6F&f=5$V6JGyXFY{Y#TYS^GaK->;JG zNE>ttbZx}u0#L7hG!GkLp)j%`DiRi-+!Vx=CEE0CB+0I!-$n422mk3L#?xq>sn>Ek zIFt1{!@27=v!1^D+xH8oE-)KJ<|whQE}q0nnO@!+#U=@Z#^ul3t4+x92xsrna*t!@ zatX=-YHwM5=e1|eMzf+RL#~&U9wP^M4*8L9>U+;EW<_w=lm!L8@oEGum{G4A5e6Do z4Y}U)ZcDKBy=!V*u~_Y3CoP?>51gQ|`WOTH5k3WB`?+Muqb5=yLdA~vp_D#j)eKE2 zA#;s~3*#KE9Qkr4z16guO)C>6OJkZK*f2@VwsLG`56QLn&=eU`fS|`gcEeo~p*9As zS<{zJlbMLS3BvsGj>Ti04;`1(p!h=fk(A8T<84SwA=5c~h1KVgLoGe`L2za1c~BN= z_Ho|`EjZ*9XIb9)FW6_BIg_c+!(Aq8IdPHa#B4#6LrmSbu#GeMv-;2{Ey4pln9yF1 zPk~D-gl2(@t|v_VUs(9@ zOnij08X-)Sc##@lKvtlwkdslb4Z^#)18U5L-KI*WV~H;6VdJc&&sBv1<|2H7w#yRvE%R!uq28C5nN0b`b0HeYeExJ z2wp$oVLw3sIn8;$cAJ0zfVBj;v?lo9!SxSkIyG6RO%V)Vwn~kb3KF?(5)PY1Fc~av z7aS=hDO{Oz77N*)AK1dsI4Jp$R5dupoXq>VefWC`z*7QRze4<>!RcVYwe}Qxz)d;& zlBGl4Of<*pb-8jr=dRx$dEPH$db^(QN`K7kFGGOUbhIQEUx6+% zW#bIq8i#%wrweLT^q4ZhRHdiXEB4l#tdZJO3Q>!%R&=OQTD4I2bc;$B89~r%$8@W+ z;D9y%(hTWVnYUyObd_r=m1K!7&)Hwe^WvWF3wP}eUa)LYnfX%!{AIAnXgyuN0i?*Z zr+m9IPNu}tN;_S?iPEzAXp(ke-i4s?7uu$z82@;&GNmvbjS%}Dqsn~6)zs;tBcFTy zh!so?Rj1iT(Y7$&t4tZKzPD2KT!nZuzP=yfW&|far^_xMCQB^j&K40U#Ya?paW@Y) z)eR{tN|xPcjLlZXZ5VNNs?~fiaU5v5OdV$0wO4}ruRjQSt7S~}8C>?{_hK1JrHeMZ znvBCzh5BaGtrBBcA(bHq{h?-vvMvJ3htZAItJ)n-4*InyCTRxY{ z#Q~O{+8tB`)gOl11IR2hjH?;mo;ndORg1kEZeFIeT;3TN{D-O?F|GtbxQ4z+w~r;0{1Ff z_bF)K3@v!Pl4kL^Td40qHBXBvIiHw0?$hIU4m^!(XIuo`tM%tys-EFZz9hx-@LM|#EZnXy%?|+FPUeKHyz<~6`35X*9tibi(MEQSA0DM$7Z5IU)c(r#khMLZTlYoj* zgfgWisYr5^9*g}{SapiTJa&tu*A25QyUWMae;kMl91z|0f!`{IX^v6`aU`3}UQT6k zHk#RdzkME(`+=94?d{2-(OsL4Q=7&O^TKQ6Nx{_kk`^Ao4J!Rj%Khu9Kbg0qB*b%U z1}k+lp>WO%ZvuAWP`2#X&krG-{{)ODN@Q4;suY))Zy05pvXM0gMZ^`~-dB}pjxDk6^PUb`+FI4j$!G*58^ahV0jS|ty^5Iy{Lj&*buJn6{ z6IIvdNsUUGu~?tNW2QOm?)R{V`}l(eL?VlmX{@KuGOB!0e4l(A>*8c+s3tz9&Yhy* z@+jN2R~ciKt|tskX5#BZx_U{O&^ab0()Wl&?9ySAUZlTAxDtwXnt(xQ4`?bMLdtCB zc09Wl--DbR=1Beh0x$0sgxu7!<3PL30qS#O|GulUTDAI~t912{gk29Tj0 zraGk>#$>h5QH`QC>z{784QyMosaW&ju^F|V{x_iaMbJvCgQwB7Y5Wxu>)`sZeGFGU z;-fk3(W^CE1>CUtg55qcN0wk=*LcqkW4ra2e`)*2e`$@r1C+cD0HViu|27K&F!cZB zpk}pKH)MeB5Lvo2*Lc#xN=i!-S{$iOzojCI*3V(TWVAW8xq}4@t|nl~5;Encw!NMk zZrip)x|~AyiWBz-^4UAPd-tyAX=5fy(>$uLIPPcK=l34B-R#WQ!`fWmpL4Oy4pk#r z5o|ch_HHV4-AhX%V zCJBvvh@vxo9KlUf2}G~&+7zbONkgKTZ&WHN3!JtEuam%RCXXw*iPs1X!V{j6?@}G9 zxo~?{{BHU@G-%+TO&=K}k3h*44sA&VmdaTvbIQ={Tv{Qw;E{XGSCLNxa7byO+}f3< z=q_7rj{-T@_a7l!9zZqN&5sX?K#s*U@LL*5^Bg893u3ktQ?l?LYmWUqa`E%V&v78b zG(`=KPhA+Xi6KqNjV{dMn3i&C(8sh1!+{pl`B?FEtR!#hF@2gFtAfjc!~>njsll-1VSy8KMn17CCi#bRmBtcapA!p zFQm5zjCUdFwp(iNcn-99S6;kUjJ%?YDO8D?jLek=9YqT!%;w9pN_(S|qYsOcW+CbT z|J2EUq_`EU?y`~LoT^NZwqi8zmRP~n+f((^9sirZoUEtm;P-UI$+U>tp%Jvnp0sT=YeooxT3>nbIH{HX>R-88DzvlQm;ISRh2xauuRC>6+DfkrrJ7# zG`V#I!M^hrfeCA8)EMB#Vm<5^BB)cnuWP+(Py4q!G$};cddKYV&rEp6f_-APcZb4| z*t_r$c@OuDZea;Uc!-OAEWW`L?-W~D{XKvmYJEGfntvH*8nUDJKUDKEF>RBDE+Q#sAk%Sm9F2w+=MOm4G0Kg{&9t)LK!{hCdcQ6anM z{CfY3Oec7=^ftvkdKR_2s_N}Sw$^mR-taskdJ1;Gh4kpNwB^{_DJ+Dj4{cXrOTreO zXyLgf++l3;otJlYUR)RCVqQv~is0UB47=1UDCaH*pYZYF z%Wuq$C88kJ8dg*=#sK)0c81QH#2Vs;$$vw?rDa@O`v#ynyo0)Sd2eDNQl_!~mzSuQ z&=W~|?HzT#F>aQ__L9bOW)4rYOhd?$V3mlj(mVxODEiN@Hp{n3Q;oso0~n=7>U}D$ ztp*B6DM=xl;vmn2x+D^oFKx&r!m)-R-I^edMp}X7Q&GBE&&F61JyE48z=|O`2D4uD zdV|E2&Dyo$&Z(Ms0HYUrT$qYK-QQ&K0qR%~*FCIO9-NmTaQSRTjRvy(h8Hb#%2%$U ztxaAP!gjg!0yyPPr(N4YDR8f!k3>1tdM=S6(iSg;A`yjTV`4Xl13&||pDsr6>f5kjdn=6-ltSp?)4$1(sc@s6o)1^-$z|ItB_<}CFkEu`k_--s?_R$8$OFppNwPg%namD?FM(R&23+;W2P z^1?B`!S~lY(P>+O#g4TKtqX#;RPBpbW{gTM*tX;$3)L=fT)woG7jdrkxN~OdnI$Lr z3MAPVW=-2vATHL{Dd=-}gRvTVn#4Xr{l{`arFo#pLO;E@$V5Z5;!4`fZcEJN&??kA zn1|V;iQPKmE$$0!S70&KDXk$H&E&>D#51x*tok)e?q6bKFAa%d3>UY#Yg&piv6k=~)<*PkO+;?+|%Al&H& z0bX}R%ZQ*6)rG`#L{^gR;|etD78a#}I;cQl-gfYYk@MQdZ|Jo*kE)YrGLb3a2@+=w zO-R?D5E{ar;$&6gG}Bzh#a;r#a$twwb^E9&PhD#tUB*Ky=++fhNN?`XIb#FdH%<%9 zxTjm^Z;;;ha0jv5+Qm@aymV?lvH!Vvn`<;he#QYs%*>qf{@qFR!81sr02G&rUWR(Grc!Gqb- zu;8~C{?ZWmf6mQKHR(2m%I^%!{$r7sdzW+9`1kkgD|8>6lJ8g;SRMfyft50iwqZE6 z1ce0C*osv*o*dPh0R*0;j2M4#D*^? zJCiRpE)Qi)727nNe?C~LA`lN?m-*A8T68Ml)-~6!(dAlO!7^W@`2tcKQ4_@ z6Um1~!<|7=Epn!X9P1qnE!xKR)9-|uvn#E@CBSv;W-oD9IGO)fhIi$FI0ADTsI?WG zI)t3YUt^E@y$G#KF&f@Czf; zhXd?{@J+VNO!MTRMU5Z;&bXRh+0Cnb%>2K7zJKnKU5DlqVERX>1Qj@AD367i%MN!$ z&;%OxGH``=Meqzh2wk+rsDc$N-w!`vI{}@u1n|Dou1&p4D=ibniLycx~dW7Mi-Zz)Mr54iyAqq z9Sd$Zo(N`lX^i*M;@)%7#)y+aOq+=uhS%oZU`s<0Rkj8HW{J*+|I&&_>lU4;u2DUS zg)yl7<6sP08G^t9YlgUd>e61k&vtFz7RfOf_{{|j^E&?M+e~#mUq+_JNE3CJ6SCIg zJ{xHSyegj4vnRcSUON`nF0$=`0|2>G0EEX|MA<{U!F<_mTmix7xC97;up&OvCq?3n z_fKiFm6cw?+j0Nt1sAElVLE5q^7Notd8;pmktdH3=|^WMWhrY^0OLUs%%(E zWkQt2Z+Rzc2vJ-Zj2s!otMw{2kDl*sKzdcF!spad-}@B)%t*J=O?TsoJ24I{Y{S*W zWQNa7f2Os!`Mq;ePD+l5Lh@8|gB_*j)5?Lif zu*E9}ZoxRV;N>VLW%lx&8mi@8%T^ch)h?`}n_NzIqP8+$0+$lk3^ax2dj}~&2q+9? z4Ce`Ww$2FhTvw!POSL%oGWN){hu4TqNwo+ar|HbRJ4a&uR)16aw3eoSc46w2MS=%Lw^Q+^6fv6C8At+XwXfjKu)9^UUPC_*E2ZPNuBZbx{+*s@O%z)N{kx zrkxW=#6)`Z86(7ZIa&fWB!iTK6stc@UVb3K2Z)2{!SPx>Y*2J`1KJS_rc;D|co zMoj6M+U@YCLNX=!VY+Kb6DvwY0vj{)HhDs7RTBC$y$i|qg-VdYu>nhxjw%$P{d$4g z_zUTl)=XJg@CeF!2Nzv~ypGjMdZ{vl!K|?i{fQEv2GX7c(?z@K1aar*Y!o$dD3aWa ztfsh)ra)nzXzBw~9V$0W(*Ro?WG1Z6fC;e0YZ_>|>(QMpuy&CxoH|U~SOWtX&n0u* zV3&Voo9Q0v1Ga`hZp;M`_T&$y+Z+~Od>=5rzECi}giSzY;E(;Hl6TSDOuqhUsfM(HdphDTK+pG)yz0uIE!DqQfPWCN^D4 zbKQD#CkGEsFCAT2XGXSIS7c*tUenj_A=1=!tK-jj&fnDAh~x7f*o^VZGN?UJ zzpO$fo^ejvcn4Fe^i)+^PNR;a$DUTMrYObCtW@vI1lDsL3m>AqiY}VcXU0=<*%yV? z9ZfTk+bd<#?wLD;3}Z>^J8{D0yStsVEcW50g7`&#vJs#7Ex@k-6|kw!Lbz7-)viYd zvPqK53&8&+%;E}8^oB}Rfa-?uvH*VvluKBy_FF{3$ItZ|mTLj0b8eiqs+^-vc$h$?cAt-dQ7uE-sQAB=Fa-V$)1)G(zgOf|UN}%GLG3O%X&WZ|FZ%8XD*i2nxm3&kRMk3C~H& zs6gkTKgDwz+&KIo|sx-~hu6%0T$PHB(lag|H zK>h`*_veIxkc~mQCuE&x`^edkdw8}znA}4?-D&4bV*X2Y7Y{-hl25QPMi94_SB%@^ z)&fH-UyudWJnapirKzSkzvdT`;&p+fCKX{(WU!x_ybxPL90Q~U0bw*rQFstbVjM(B z(=rFcOiAH>uKN07`7s3^Z>?1_d!VrIG zcN3?SET~WH`vn7KYOw0dw-eAN>bmLXT)spWGDn{-?at6o+86eJ<{l(+a>D)q?%@ZZ z+T8y~Vfue1*x4$ZjtdGXx}`xyzqC$=SM-{yMFE_ndZmKFB@J%IdFbuO;HgnwDGQ8j z8VSR_`di-2%O3cPV$6Gx^iRPu_^igpSv{-TM)k4UU4CC64v`6POhtZ_p%0X+a1(tR z2T({*NwD?yg)0T6enR0r9jkMIS`EO;@GM)+Okox#P`i|Mx}|-(F0;~7mUiC$L95`G zgB)uK8-duzv_oBno!6)U`*#SBm}7<)C|@CTPz@ z^Jl6-joLDWE{9uEax2xKe0#x!)2XePvt-zwN7L9GB<}jMdx>__Km(gvb;-U^21yOf zrTJXfrk1=_9(|_-YIsS~yD2K$;i{9P;_mn{BA*jBlFL+loe3gMW6$%~t$a)XamccL z5&Zn`&+2({p9$kg{Y=?}V@T+QW4gQ&%;ceWrQ}rHiVQpb&ynWXRy=L=%&$@c!tq>o zY_;^>`Cxv_P+mF(D%1RmG+~8dCmg`?Vq666B?Hdqs`gM^7TPL6WJWSaUL>B{NJiy0 z;tQHMM4&djt_j%=MuYuB4}7D=~a7zH4bOieKJsxaBs_5|J27&$>FH-syVusD`0=~JqEP>Q}(X#=+(IcS?VfV)ZjY%3J_ zrC1Z|`zH{KQaY2{c%u!Eu)tG%AsVf4_3sx1PFJxMo!2`gF$PysILrP%g=*QS1_@W znI+$abl(ep00~*@F+)Z&ggBLfKHAzbn|7wv^}c? zrem7elg{9jF69Lo3^#4lI5%Zs#m^?`G7L|J6Sh**2x+6FSognI_Sl|?-jSn+9wgN0 zbZ?g;j&D~TlR=e2;~hAF+0tZ+1V%)i#RPJC%Sc~b^oXY^rbHba$G!gzR#5FR1=v$f zFmSkqzLhOzhXpy2JXRqT28Eo;NJ*ovE(*?0@RF(r6`q~FXrVkw78R{9T+^4sbPKBH z2F*0^w(|vkoQ>nLTWN}nxiksJ>^Nuc(b}Hyw7A)Bov|t6lcq^_oORS+n2$zBv3GOM ze$4V#X~7trl6$Ov9?bcnDdQqZKhs`w2Lpzma9k8jCCAHzu{E3(Wt?Ljvt}y2TS2=* zbEf5Q^NuEbsJunyNKWC2wjOA%BBb;m9FlM14)Ek0)YIa(AoIy<@J+dI9R|PSd z?qLz8I!dDf(~o6b@e7l5vxGyJmDvibBS@tlONPeE-QOf+C!@-pkKWNU2WBtKX05BJ zkq8?aG-Pb`Ej8l9XWhG``A(6SbbuLG89D^DL9NR{)bbh{0g1iVjgaQqGgb1-i zo*STg7qzNzh_u{(-0`-WaT4r(_!>cqPURxQ(oHhIowY6II~`Uno76QnL2Z}WPFgxb zLX;kLJ>-^}3g}4gQit}-Y-|=AS&W<^i-a*4 zV10^q3w>%(bp1s0j7(8*$IX$Yz`hMx19LYw2eveSA@s!R{Z5d5V)gfOgmcnOu#6JF_{A!U)oURWZ)Gwfz zdx{Vb7_%2qoukCI3HtV;&WU#FqVA#dhKr;{m-=was#HUzF#Md}dCJLk-SPueC6`Bj zmE|3kWt8#^GF9fHbFA_XxeD933XXEA5BQ1_2;HZVTvR*baQbhGS675x;7$m_EiSke z8z&dKG3Q~+RSJ=*zLUKBO^93W(?d7LvBa=Dl#*AUt;4?> zd(a5p4wLu?mNT7TP)rDNibkPF-2z~b8g2`9cr=ZlWB-*=kcrA-U*xdUYZ|7xt^ zBDS|=4d4*NfN0D4e-UZ_N7o`-#l{Z6AbjZ_KcMZr7>^_(?7i)O3kiavRx3~aH>*xInV*rvN- zBB37jl&9xtnWtN#nSo4vt1J;o;tsJ`>!iA77&BRH(FsXCb4rTwf0!iaA6vpK>YhO) zze6273Z0P*)Mf>=EY#0f9$j_+qh&!(%1A$0wR-Q@`Oenr+-byUEuWf(MOo-^tRAx5 zjG00WE8!rh z@=u><*{>MWTzNAdcWrjHOL8lZZh?KoAiX@EnJJDEM;pIbC;p03J>5>i5Ev9jD(}e|Z5vvmUPxcknlx@IM}{ zd%)2Km~A@FTq< z$ESvUqz`=rkLibZU4}UA+bz9w$E$~RNnPj>-*eS}=j~tz-;zD%1h$qvqdo4p_Vbs2 zqQ|?1bxBL_7Vf-@e92xC65%7f#*K+{^#AaRQmhp)fPY?wl29)ru4sO8se*iDQphx9q^)Pck|Jv^8i>G<(JCPw)T&}ex>!K3 zUTI-3ZW5tDm8Ymm>o>j#IIdS2XetU4ET5iH0z0%`O=YB5AZIcG$zX}N|*-rrjyIzq{r%5`OHizJI8!mvY#Aa(^r5Q;Eo#zN-@{wdg zxv0?HoUaa6<~ZyV<>)01IA9|`;H@rKAPSb+#2hPsxz`{M95C;qM2mSMCH^ccvzHzk zN_NGf6O88CgCwH}*>o9F^(c{1P^F!G>(J>m)tGS%S=Xm9=pWNwc>AN-5))axx(|tk zk;OImt03Cg2ku#JoLh2ZjOC<2f+j(0>X$?Fv`5Pco_y!_S{57F!h$=wXKx!P(xTRz zCs)%D7?7+P4t!{VO}$CPFFrn)Nfh$@K(!BOo}#d)gmkCC!+et zHWWcG;`-uwcclxIu3a%D?r=wOrqm7GbBO{y+H|Ef{6jx|B$)bhPByx|M01A0Xw=a3 z+zh)Jh8*h59R2X|PCOBb5+ado%?i;1j6j$_8|~)|b*8F?OY>#UrK)5YZFRjwSh10G zuTY&88udbv!Dsn%W*{eWPcEl?qJ3+#dEz4iH*&La#+RkZVbg20TNxf&XB^XWi-)i) zjZ*SO!1Jjd&1#v$l<`ORIXfsh&4n3s!AVhBg$`i!ng$$r(!oUaN0uGdF*|%9y?rB_ z9-q0Nw-cZF=J=4}-vzgp+lx*dC#MCgrfOe>Q)sL^wuI3RZB&}1;2^N7N>xJJ=E*cR_1Akobp?kKWdJhPZP=BDFC zn1(x#m!IUSS6rylR+NRnuMitX)K41iBR->EzY-+~OUu~tqH7o+#rVnJ3*1e6c<#yZ z)wuW(=pk)pC@5Qwh-COR37GBZ<@uGYts3y9i7RRUn1~T=P?4mV(k_F(RvVq^l4>dr zK657)WLR<}2+VNo~Vv@!!|Cm1^c5vqYvul2Yqa{$kjCCU&)QpNK zHB{kVa58U*%B2wuRXRw9%JM&4T%kt zudu;r&oEh55QtJ|s>0lwDq6O%-YG6PGE4_}wZfWYK;ZTMYD z^=?`fQfiCpGU!6J4boO^UyUd=u`b;@Wx}-4619e)ULC1DVk}kCHG+(ec-l$hN9BU| zGG;oqiUB{4f+lQcAHEZ4|6xorsZf?ebdP<;&;40b~-a)JqD{IL)$TBAj&GZUTAAQ zJ3aJY$rV9|yxoLvV41uf;&tOnb1fxE?pUxSlab-OS?Uq-`j!)0X`c4R$(;EW9KR;8 zbOI==k#?=+O3!AgvnD)`85ONIJ@0T)pf9Y?o#SWjNfp^ct~NxZ^;t5?h5y>k&TQo< za8!{dGM}H%Z8y8_ioYH=D|V;e6ef1EUQn~z5iKVQ7jBaWY^w|VBD#vc?5}t=eo(6R zL)IZc<^4qS2bVe}1p~ACVP6I6;T;R>i+g`T%fq*^MxVc$(P~q$J+_IPL=*LRHDz**EhOyv_cos!y-a%mo4}j3Z1UIZ)v&5!}Gr-Zt0?;|SYe z<#TUKrQ{NH^=H-t$jQv`esq%xK@!{zN#m@T1R8FXHUWHv#67DJ7o300C1NYLinyq&a`IxWkvR&K{_47Lf{fdviy z#pQ)hE>%WX*MG`tKy!d$$H0KLX{3T!h|Y*cIAS=Y!S(;p^-fWm1yQzcR@yczZQHh; zm3F0V+qP}nw#`3n+rD*9_q}&?pFR(!e>7!8NYKlfhSJCb6G#sxKBRu!s}3LU?qfzyhf@@+&&GFWRuQ5(JPM138N^Z zzU7b!ru+nt4fkE+q*N16VI8rGdd#I<^flrbeNuNLphAKx{E&MchV1Jbpd@JVJXf#I z+dm8(zK*v@hk>5@JUQz?6tFs|o7THeaT@K|=SSFynQG8ePrHG$18)Pe>FqW~HV`vN z$M(B4wEUv}Fz|wmO5()GNJ$t4q!{a(`>ZtSd0I+Y^`IbmL`F~!DAOrPhmA$>w4B#J z=Hn-~rspY}q&32Ep~FDB!3JqfnLEln!K|!7Eo757tXNGZkFVn}eWbZSy6%LpN8pRu zP}r_y6xUIDyQ?=c{|blo7TNU{5H&>75d!!40e9%cbb`cu{9MGJg-rV-ECoo>8UbQi zUZ~Np0o_Q&9Z1RM#8gvaNr%jaVA%D@(Hg;GEO?^CJse!bWHA$AtY#uG*!Aeq98VP3 z_T@-hUuz4FYK7wL{ZrAFzG~VZOlqgGC+4{A-!v=#k`s zq#w;$X-}GJ8ueLsJ4XE={kGPH8YJILFtK{);J#4z>Kd=qw4bWz-xjYC{k~{#wdwAc zIv}eNOKANmi1LfxiaAfOa@)bABMi8X^fCte|C+ofLX;f2>1(fwep2^V;v^z?V7%Hgv}OR$4cQ*hJ;&G+z8>o9pTJ#(nF$>2f%dR~_W;ne+&S(PHz zr55cvue3qEI|ouDqZ?dBI+nF-^lKMw8zt9QGDd~Qm~Im5BPCmXh1KrCU43n+Cs)^huh5=}}9$;#` z=y+ZPu&rz{fv;?t+v-F|JGVyrJG-uHsT&z>T^Z?b*|ceuX^}DgQe|~~>F23z;g-GW zratL*g~IDN^i;Tiu_9dsNc5aQlHT|edQNddFnNI{)E5PIwdL+FbQjtAah7yS08Jp?EU_<$1fN5l~bk3k^aABe-;`*8gJ zt>@==dMz&$hwPn7)vu#~+(vM03w;^<{!AEGMNl&P`~0sgA?vunc__qdY(#w3zgD2ivLe>OE~1A zaB2TCa0gq}pvN%&d^q-OJWe(iJ76T_f?(ltIvb~QfEg0|N3_?BOXq>vNnuIU#%)sx zBb}fquz;>W^};S0es2_V59&4CuTJrEp$Iepa`!+Dh&~-&mJx_TyZUVoRk(T}xp=3a zTyb65WunMHLtpjT0pA1yq3}{ag(>uu7mokvYF3|d&bIAwCt$WeJOM^-K$qBulD7ZZ z*ecbq6L#Zht)|a~w59UavH)sBxh^~QgD;rTk*?*$;6s)Tf5x%n=f6pD-}=+&u>MK8 zoBshJH2-6gLczh@#lXqre~=DUtL^+R4D#E?A<=q(2$TeEaU!;ac3mYw4FDiBWK3Pk0lJnqw zlGT~W|MPvs4#a-T5kzP_fGNfn70W;!OE7tqqkW9(g&Y!46`+loUouz119grqyi(4yJenF;Id2uveM&OiH>A6Zq!qsGGrvNn+n)vgK@e-AS66Vq{Y7PTt-xW=wG-&an@_u8?PG(+P7w@w|J5-0zW3 zbQ1%RJOQr=N;pK;ugz5w1j~Xc6mg$#pc5oB@vor2zLTi#N&jWxMqasU?rY=r^^8J% z9V?hxdD4Cj*biRCt(Z#ZtQi_7&I#Xs1&0M7h;<1~BSvTbgogkU#nR0n3+g3VTEa%y z<{839;OE`GeHZl)JvJ_02#VrmwaD9krHb(PdskvWIe7T{6GyQW_Y*&wD0t6)g53T;6)&BocJN-|# zTFqL1Lk-o(MrfZTO;8JxW{YX!^t_0A&gEwOR6{}?TT>HxZi1b>y|^YSx+Yuda+2jL zR|fDRTOg!aEDIR@Dl0z$jL6mn1`zEyUc0;JIL`96-0%GOe!=a+yEI^qx^k9jBm!O> z$|=jF-vYJq590r3+T^_BaH!D6vWaOq&THjf4vtt|fn1W-eLieC3 zwEOB;$ALYME|D?G!K{m#Yo*# zDc{)D2Ykod!2dO44Eg$T0`{-yA^d-(m(>icoK5~~H97b{0kMrW`@{}w55nS2DMD0o zO8ej;<$@m6I4WRKi=-FXj-=`+K~wNy5vcN3J;{HB*pu`TLlQaXtlX@7uWYTWRq}7$QdLRWsam#!qxhM?c}I*9UziG=wYpd-t%HjV0`=!h;EemRZ~~pWGc!p zrE$VlqW&MexkBI5S)nGS?BPMds`veJ7GXPOb{HShS2$@F9QzNeT&irqh{iPmBhR;;fWf?nH(hV%fIH4u{seE{5#o z@Iz^Z1Ow>D62Y=A=^n@9+YSF+rjWh8t)lcVXwdz0-1?t`#{XVNj{gxfqLco&7xn1R zAZm+FD{z6fY7KO;1X8~ik|>?hFGl~k(^iA6-Sdvf4O*z4aQR(`w>-sLblENulqX6r zSfeH84Qq3AOPNOIzsFf`)4nrVXRAB4JHIeTo*)Z2iVgY2P_xt+|5aT%W+X4VtP-f+ z8OW+(8n9BQMc1{J81i?Sgri+IWL{tyS*CW7aERjXWT@*eu}1MiD{{N2SdRwC?VIpN8K9k>T-AgPVw@{6rD?8s^3 zr_s}M&fy=ECcyv!YPO=`rBl}3!Bv|k|8 zHp2TiP+>}eVO)XN+Vh%BSUO$il}>Z<&v+^~>##^FPyP;RY;GZqW-IL8d%@Xu2h4Aj zO7mH?4)!w*^l>IUZdW;CG#?Ip?*|h&)`VuqegsMY|FHf zC~Y56VF)hPsRAuwXm!>p1Kq&@C+)$45J3{m4C-aD!9CyX!2EKd8q9NP9ukM?TT>bK zD=@}k)w&lV9clWRV<2|=iRkVHazSlo@J8;DQg52wM|hTth!Mee1) zEk+-X)GpkwPYQ2UvrE4a7wL%qt=HcFWF_+h~EVV@=Cgeq(N zqDWBqNA3%2>Fd8izng?dRV*YxKtekINB7kKFpmF^ZMf_O<)yOx^z+j5XFGG;I2bep z6iQksO%g~{aFB>wEFlj(An&&@lK{!MKKf9#b2WD5O0}xg7ArJeUW4QXU6^zEibbTf zZ<*?bMWypwbJ(%(jJNG|S9;gb+Wn8WPp{Oq=gs!RzjvbN4DZXP=g#AsErB;|9})nP zU?dEuy)8WQ$%o44T0C*BFSd7g-;~Z~Z(ohh#wcN9F^>LvcVCX~S_;KAQqHDL9OLEO zka)*tf1i&)Xy2#D(=QUDeQjzuh2<`;O&J5>qh6C|0Kt`UW24==lCI@Vk%H~ikh`~+ z7;bP#uE#SeQpbEhbzfcPmD>U(C-r-4kb8)f5O$=l9+_04(G!jnR7X!ny z%`GOfTbpNgxVGKRKN7?FE`b1Lc=Gc$lZ7P`*{Jqi3f=Z|VMOiCE2I2pD&6ykJy9SI@(t6Gr*_xuaBkbH zUnD~NYgi<|;~hrtYvpTNr2N9r&d^T#s|rSLu|fJPgu3sHtKREy>OM8yH@EC(;*?Nu z(^zjCM<1Nt#Xr@kY@JVGq^*$vA48+@&Ci>cl zDx|RtEbHn>wpDhP+9-7sIq8~t)@Z6qVoMw~u@rW(1LO8^_7kz$3bV)-5r82ruz3r) zjw4jjsu=c0VK^1Ls$@U1D!%_Wc@s-jbdzYJri8+IH5q_Re+=CHA{nm*`t`z4){+R_ zMEB?{zB!)p${H@($h7P|9JnVVBzZc%RQy8Y_p0P32|CIFR{ilxi-colQ&r?XhUd-WggNu^JqV|jnq^Zl;erN^ zwYhZ|D}C86M(hh{&ZmXcpZPO_IC&WhGaIu^q3o2X3R0Mm4&fmHnU;pM*7NTiwE{Xt?VSJa;>Ob%X3TId ziFJf*C5GEo&d&3!*2Z9l3{$4+Sb7yk){Wy^7gr+fG1{Q;jupAaDE!?ev`VcRRHXY% z=loVPLM-rjCb>yP1aH7_{Y@Pjj#@?e?a5i$L6m3L5rt!5I+g9LymHid@xM#?XEl3D z%frId?kT8U&IiW(hp{6jfkCoPB_-JU%5n-;40u_}HB73^%*?G5xt{3ljc+KiM4N!Q zutrE8Ys_skSJosFmgAXZc}AS6EGe|lGE(13zN$D2tNxPW9TZg7gnOV=s{Us49!Scp zVpkOHP6C%El!63}dFPX{4zw1~z6PsC?KE981ykR`rXz(<7hxp^%t?@>B)eI*L zxZ+w#0jOHiau!3_7qq!gORQqVwD;JSB`5nuj8dvBVwQl_s(2$ZMmfsD0xVUoWYQ-; z5{xDs&oIYG-q*(2;k`d7mR4ET7imQ{*0xsCF|g0y5HA336A4g8SIu<&ecPrz-gEBH zu^CH^iskGjM&la?SgZtHt|Z^!X(-sY-;%)2lDfyO_$FEas?NgKDRM`Lk~S zKrLFAvh(2jo&wSplIiRjSF~y8=&Ncto&a8J#zjGZVG97!bHniaA~5g0!mLPF>KwJ( z6J>HWPM>m6KX+|jW}RYP_LDEZS_@x+L>Qob;)2tAgT4IB*BNb53cskffk8JeOrtxi zd%1>4z|sCRbMbpiI}0(7_#;FPtn+A38&?oKzn=Xf>RH0bGIytxL@e^|gVBSZwj&0l z3(a%{kXU?}zVYuSTcNi5DCs!a`B3g$mbqZ`1ATRq#W05cpjolO$xZnoXP@|Y6a~IK zkEw~9HU(8D42;gz7{DE<(B+vfxMn(Jj3GT;9rChiAU%GgLxjfyt3>G6f*mjyb)h^0m_V+Ut9Nm2SG_`U4+msN3f5eoYfk=(y+ z+g!V7z}@j^<+Y_?JC_=fhPdZFcE`r^>4lZ7fa4}V_QgrBQjhl0zQA>Z;2g7-!%6M! z?KutpOeVra0ev;?YGY4d+o~pTU8d%ic|^>g?#`wnEj1;S-x;We~zcn zB|_%pmBw)bVuuhcMwd$se@1wJQ^VRlNiFu2$Uzi5ycxVBPUj(>0~K)w)lUOl{vbTP zGjq$~G?i7eD|FqRmpJPSYk6Q?s0-HS+)zs2FT>EQZ`TzD_p%C-_l z_Ez6BoCb8X={^wM2`aPy0V08EBdFO!nISID+p!VCffwR--daXM3!& zhp;3Ypi{cj6JAnMG^7P&P!oY)!wk4me)mH-g}S!C#zk6+_^GXK6|<=C(wMTXD;z?I zw4B~ZnBxaCn84Gzd(Nqkd>L%vD9`$|W%{7Dbt;$OLV*n-Dqx!Y4YIuCp*+I=R)o;25;MH;1I$lhmLqaXJNvYH^tQ~mh#J2 zk)WUxX7jlW`0y4gsLf%*$pXMj$;va>EoghZSD1W!szMtBp-;T?b1Xd4VBFIUu6B-79~n|5 zgihAuv9nfCV>*>%xYf!_1;-cM&!^Dl@^X}I0yZb)FaXM4!epOlT@<@h`6=w>MAggl z&er#CAuF2Krf<8&%a*ecEX~L3LBR@jqg^2dZKT7JEkimdQ(6;q)>K6NeF{;6DB5OX zu$FNlxH(3Ix5Pl$bi=_s%@rr`9wmQLuIUf4G<3}eRIm8+|7LhDKR7?iEY^8^Q-a?; z%Dx2Jp{yu>&6<3Hdwd69y_tISfVEbfY%s)Lo0pW{(^a%%dJQ2Y1+@&KAZzWxwYLbJ z3=GGDWAM>^YKPH^Tnucfpbv^4CEFEdU(bkv`-J(_IKeuZ=+{xB3`*q27b%h}g2~{w zLA)$D8!1hbxyyJ$ToX%b6yTgyJSi5`1&Xvj63thW%v&oJ@Fa$kBJHHF0MeFPB%5_A z;(GO@KKw|ptW%myyocyfw?mqO#t2`_Hy0wu5aP!W3Q=R%hUkj~I~(bsSNW{0RhfbZoLda69m0|1w~77OxO`IKt}?5K^*K9CcbN zZAb-+keYY$LWN5OgSK7)O~S(ZRYsSu(j11cEkPfcDZ^+y>Uy`r;{3>Q@de_!Y z6xsVuj0@=J2=o>nPB>uoB<-Qg=@9~qlO~JxgQe<(W&qZGnJRb@RCIULB$nL2u|}**~ME`K@X0hL@>JdTWtc55y*wP!Lb-;Yt+FqZbJOh zu#M58%wP-fM1i10AN;oIKdVb1)%G;~m&X{HiPQ8BAI3h78yMx+q+M3d+>7Tc`jeM^ zeM>noR=hY{9&8C7-*C&_`)iJK`6qijjVI~{_Hs#yVwZT-=8p)-X;UDEycRGv1Co2> z@xCa>A-MuQQK-u%u&X*_lpE27hhJ=nIuuQ(NNt;w90RBd^nr;+VZB-4fu)<>q^R35 zgB&SSBDCm=G#L`QY2;?pAGt|fVabpn=6Gfp!+Bb24DG!&Yk*4HHWresKV#y&RK>^}PPB>6^ z5kUPjN!5U+TSGPtjFY_G5?Qcg29I_fCCjD=xpo33iaewhofR(RpvrP`p5eT>woB^W z5W$G-fP@+qJVO;e3Wch}w(pd_?hM4l7?_l?3LISLrf1Z!out#mIvBPSFV_8>YA6g> zC#Ih^t@zL%3fWJ|s&wlNOt@Ao%nIUfErc@6$hr*2P%IgQviubbg6k|us{jl-Risrj zL-zYLM;gQwAjVNVhYcts&`~RQ(A5XDO)R+L9Cj&%i$O1X3s;!5Ifqm1+)J66B;Yc= zdh>b~d+0$YTE@z!M6QW_aA;T%dGq`@PB&Di8MLQr%(^^yOTlh@m_2Z$%HXyFz8&;{ zXqKKiE51UfhuuuZ_?(F83Bw9p*;U1(>jejd!fb*^36>i%mpL7}oAUa`3lO1}q`L0m z5Np<8{jkVgB#c3FIzj$BVpzn~1OnXP3u3U)p%+Nt^_gCO?;7ZY_m2qMYo0Az+aEw5IQxcv zV3YqeLkt_mc_-ZUZ=6j@wzfzsJNEUwL@PUlb$#M>eU$ZGzJ~rU<3NaU@XT>=tub(f zcsP7A94k_s%ilOWQfzBazLVetR5&#CR*2CX`jWN>`VMEu#)V1G?yycSTPDJ}iH2c+ zp}e7=4H5ExaPxap=X`VKeEsKqp^Szm(|+sr8{bj+O35j{vI_mUY7lMbm0J;<4`o6> zjYB_`vn^Gko+}1>sO0z1fc@u8A@oz3kas%V6EG&*5;Jny4@w5!ee659J9{fTVe5Jy zl7o1vYrv;>z^6Ru8wBWEDCnCw=vyw-eJ}E1F5;mt@fbhp7(emYk6@gyT%7N}uV~y) z9B`2GkHfEXfg1_7KfVA?Q4YecCgLdwNjrgk|25Yp+=J63HRI zM7ozd8QqDj7Jm4GcGxm;zgh3@UE^+_WcqL}pF_KX!%2buGS_;HBMjML%+{Sd@IIan zHiS-nZFbVPGuUZu)x3D`tY9?}aEK$2;dIy9xwe6)TTJHj0aQ!$P(ES#_C-$ew6mmi zkKf^N&!Rv}${R2!Y2#ex#Jy<(^@BM?D5(Whs|M5Cg7_z3vn*~$0$!z z?^wf#EA#N1dwD%^%ip3n-6&cw@!W{oocT9AQJ)m~N@i}wGzWZ=RiO5UlBCV#Ex4@V zVpyus+_}W1hp~m?9-S#@aJ1`o|KUK-EX&7_S1BJRRvt@AzeugQX!xp-*X60V3M$Lhh+Kd@o z!z>ZZR%A&#Hm3>o#vET;Eu8Oy9?{f@v@p|;d8Lh8o*l~x5arPA1adsntkwy02asCW zCkU4D1^Sj{6K)e{gbv})hok@2{=nK=|3{FjNV2tBH>}u${ic^~eGbDv;w_ksR%=!y zxdrQS(w!hkZ`fU_dB=yjt}VOn#q)&cdh{B6xqQp5IC)xek*1r}#wzKl9Ow*3sP5b< z4*x~u8rTGfD&Q+<+L12f1oLj*3SiUQ&pFOt_o?07cvklNz<}34(G9RjEI3zC(bG!Q z!X9EjC5Lzu%zfpK*0Rp_ z>P6M}YK7w!-XdFJ?dwII%I*-PKuDjw^{V0`{~dbCVtC@PViOSPLXQi4wK)TjwVS(+ z7S78a{nJ^9*W~$vXm;Yfa%~22#m5N>IGX|B9pr8CBlx?7G`fsBtvS}S~RrpqF?p4&>vCF-zDkc^skFa>lHsv306pws}*hPa=lhMPH z)_>a9-%C`bM+({OmH2KjMg~2ULboObaZi(NDv)aXQ^fu4#z_RQmAB-UK(fD*yf+4M zN6B!CplS~2hzWtkF+nV5VVHLr#Pg6ApaSQE+C!heN&Yglq>g}S0R4lE8lv>|l+M|D z(7rs%wu!N2SbTVm9I1|+zD1%Ob~h1H;{&un1v_sF*&u&v#T*PBIuQu19C*{&L|euoTeIQ)bM=P$>6FOro44k zCS&40hfcKE89eq_gy_!U>>H%~qT2AW>!MQXBwyKdv*?ZP>u6RDI-Bn^HMk-E`}ud| zi?Y-aofJ7!8=W>4`(LG*m@U^0o)H%j>X-(e5``#rd?P6X{(>ig3*W9jfp5@7U5X8}C`Soja*`*|ZKlwepR$F)M~0T` znRra{K8r2}_~MT!Y^7$c(;bkmeOtR z7NO?$%DeET8+52@A5nF~iH#Yf(e>mhb*1wuwa#4#q#3)-^fh5@A_PK zI-xGo_JC^k?Q;fZ{Vho|xjRgT)6z$@{k{uILSbW9c~e?ne-i!ImZE3}BG!lB>m6il zfnZ3$yKy=uaH%TEvO0r}tl3)08ZRZpn^Aj$VIPM_&2c7dn9IUZ&2Y=Dx<2esI|yZ z+@9=_lmz7iU68*6N(NdsT`O4r43g|Syy#B;DL1P3rZ+_21#cM3$N><&b@%bMLk-x> z9q7!aZXYF6KV`+G=@6=2ga^b40_Y5%;WwH81G@hMoBsp6|ARR2Uw;1wI{ycJ{|7$* z2Xf$(!5_Tc*Eedt$@-IxgNThNYF0`l%^xbtn#(J6^=GPW{21!H@#+kY>B!7}#(-$4 zaQ%0GKF0>4HJeN|@-P%yT_j8J+fcVfQeSF|7Lri9FD)h=3@>o2!3A5!!_J+F8>B1g zSWru7L%zr>$)T1Kb+kw&6&CHE@Q4S`Bh{=4>7N&O^Uq&xtO39)keXr#hLi{?RD3Yh zfC$5la&EFS?mws{or+Mz9XxIWcrpMIRgAkex^I3CJ z%DW8HY4^#>xKNkLMA2fqv2Jli2n`ZbrN6OSKgSK?LuFE7Zf~ z?}G}7Mk&}s3o!v>2}9-!37Jt4vP8{c$(=_1c8Y+^HWd0xlCLau8b#zJ6eMSk%oX_& zG_&uElC8XdY8EKtB!X=_wTdVL;m*G8iP6LGRP!gcH^^0=?`PZ>=31=LqpX^0%Rl%6 z>X(PpP2YI2w#IbLkhKZ{2B)Ss7)`nJZ4DA1joSquN{<$c{h zwC9)i4cpg6ZjT_agu*ewqa46*BOFQC;t6*#1E@wkrZ^}Js~p(PBaC5~DD!4A16U)* zRje_?2vOy2n`t2z(O9qPj3})*jdrz+I9gGhYo@rNJTaq!X;jk9ZM1!t=IDkM`tH1LC?4 z(C(5yAeb`cQ}K6NnY}csg=c=vW1kZ)B_#547jJgrAzAsyOS}|Qxj_?&Z`GJ|{ODU! z%B{&yuWt*KdX&e^pTWG*eAEJ*qosBnH0X=ckE0+N6_%T=`zm}{+{KAmE9cTSuqGK7$_npv|4NhF{0pKy$JA2U16lCP>AI#IsC?!)jHcUMC`ulGi9w%IU%W*Z=TI zEVxm4yt@IpN8}*$4*BHsixqQx8e{?n%hNdRQB@%u*}d8; z(oie|ufYyb`(0;-K87DB-`ad^t!D1!w|L4&sK}pIZbB2MldM^&{cN;N)3lu^qH@zx zpL7vCQxjpQkv~GGX!WTS#fFbEiWwVeK7vv(;HH#i#zSfm{db7SC|1LQr!>oj zqXuKLSc|Xtddyh`mz$O}CjI|gU3VNQu`IYlR`;t0#PIqgU=4L0t?oA2WvvY?-%d%h zho!--dn)dr-Eh)NluoK=ax`ddP{gOlEZ^rof1KN_$ccbA>auRm4|V>o5+Ju=jS!4u z+Aa+Jzypu?z%p(RQylO{F>j9=2c}?+KtM+-p2|<&fv~vUBO)u-3&BsDTTsXoU#S5~ zJQKeyMSG>@M_aWP+z2yDBSSz?>*Z2~pfoeh44ciGoNQVOT;b2O<$dSJ^MR3$L0*z( ze?|2sBLC!F9>GlL_B34qA5r|G%n2jg-!1&9QP^S5JhP0UOi3Dm`L-Yo?Nvu*JP$nL z2t3rk^v?HE)Jz%Dy*9yt(8tu<;LEBbIALIRBu)T}gWxiZezz;wKtgEthE-3XDB;ff z_5D*|)|}@)gG`z0o6;ckO=Ch@d{=xt3b~wZr*0dsb24|%ucaG!jsyDk6dHT(MR)iP zdVh-jXpVX4SQ&lGlctB#76$mt=nEc#0^SN|o2NsYOg&$FF?dMkl4({`^p1KZY_Jr( zrd=0__4LMFSJ6asx*ZczgdlXtG{p{$uNTHb(QJLR zQKV`*hVfTxCX@bOh%4z*0%;vIm(aFp4HTRq&DA8IVHRC6bEohg0OM_jTuMWx;e|v zVmGY~TccjhqHinHD? zFem;;IPRQ6c#IW%CnlZ-C%na~PN&7GK&DiND2D?n;TiRLytB++{k@lLdELOnK(KH|Z<(k|JSwdRG@u)7wk z_0_O#qZRz+L-Ve#7S7Y#@(yPw-EGjdrRf;uG!!f78G!-_%(kRkcS}F_KFE9DYZB!-9L5-Tw-cti7V{-MvI^nrMUww+jQbg-w#$fr zetd>LEHZp%w;T0)kEh*o>b-9@_Mv_)VV6oJf~!7#BH%(K|$?ndGBh5DO-`WtU3Tzm`i^~qLYNG~av34us@^nv;8=J$=IB399R zmLj~559Q}4v8AQ7S9LQV-f0HQm=1sY?#Wn}Mi{U>F|U)0cTVmaYHDX{k97VUUEq$m zOP*y;t+41{`i9#)6Mz2#;cr+K;T)6PY*V{0a9zPaUZyF8HHiEz#uTk=CSCW`X|(se z&8q(38)8xF+UDz9)HVC#8Sm|ypF`(=VUc_75_&-|wxgYeY+Qut^-z97V8I%KLxzOs z+{_?9Iz`;8+h+WuKgAxn*ZL4wD{J>dqoJ=AUSo#Y5&t5<&$9=?Flk2T@QEg`-A(0ryAIIzVldO+ZtruP({Tz+cQDmNdVKAS(14Vuww;_1~{=;pf&BWYT2!&AcwigYc z(Az?wl>O1okMNk>`FWWnhaywk%!F>G!(Zi zjIO@v$j#4;c%Q*(+x9oSSl<5WoAx&lcHhHe`OU7O>B-GcPQQ&?=$G$-{hx{hTdwbG zaTw~oe*RB4w4cLcz0Ix>m>;}8Uya1oJ8<@&rvrxV!(&=J@l$m_7wCMLXi~lcm{_{5 z4vKlvaz9MeGOZrYCf20{Ine^zt^DeEe+9jzOK7-e=vNOSka`8GYz8pR2pUVR<36g2P5X}bz%#;cXtKkM6z+uc%m9d`bGX+`N_K?RmmB237!)z>`Pr&**5Jv8{z zB|`N}844`cDLk%UoU6Jo6KEG;QX53|5*GD+Lm`GyCsjzdbdfJ8TN6j|Y3teem&_=Y zZW)q8gP<5h3hylt-Q}|JMFP>n`xQav&uLS2Y_6lv`BOC=14mZiY96|b4#Ss2S-fzY zE8MMgkqGd?$oVQfgzB$@=b7&9H7IF^T=Oedj+y81`m>!1VPfILcrjThdtSz@*|Aya zW-uMZC)16OTqhAZZbT-}4}=o>TzU32jld|IBg}sp*Huk32WTRTrlKTqjyYCb00-5V zRyHVDLFYcqSZqdL@oNI53931GBpmx`#6oXD#eGW!iCD0L)htTL zF&TtPB}p!H(PH9uaDXGphMb)KIfXsd2BL6uvYUDE^7K;=GAwa(noXU{eTB`HV7d9U z0>0pmLM|-JRGF7?;y?R_mCkg+#JJk}MnrsT_^c4qm{2q)wKEzV8FFH4UIBAnsHs!pT13ltL*9dK9cS0(f5#_Uhy>BQ6Y*3@x-E}YLzf4k&bBXiHlg#U*<)f zoTJlPk&sycI~H?i&Q=O?GC4}7;sV3*5eXtmsn}_nDOqJwILE`}J2By|3KHzzwIWZ( zN{*k+FO!5WRK*5H>=U5;&}oUJ0uau_lCeHA^b;H+-z$Bhj@$W(XALUB)XmeV;i(~} z0J#iz#WP_N)j*huugNR9f2V26bBGRy3V>Ik;kF-tr(U?4)|t$SIPa&T0cZFB`6>*X+kQ48oX0M+4`mS<5qgOU9Hg&t!nB>Imi7DsLk0LyEIi6nXLiQ}Lj{dV^F{NB4SvG!4sLrzRK`v>~w^-q1{E z@axT;aO4hnV=Fhg*nHLEbGA!wHbJK-oKRqznIcz@7@kTNkqU-~JFNHnJ9F7l?; zuD6CXVTxhL7jG7IRHBIxT7|ByC_?1(~YE z-Vv95u|1v86noz-CtH+vGkOb{S^OXs_@Hr;%ji(IUZIIMK8$?qw_yt%HY{VPH9HM9 z91^N*h#9)0^SltgX&8#e2}->d#se&4ZYpozn9-I{Frgd~b9mw~aE0cBPLNG;Wu$`a z-Zeg!<8g-=IM*!uJ*J662A$kECz|tFISFN#LwYeur(^J-&teu2#ZX*2_x@Sh;*brO zlYK^QlnLJ~TdQ>Si1B33R9qo)LS}@DkXkt6SQ1lHW&~76H84ypDbmr%meNCk6NQwZ zW0Y#Y(-2C_g>v$qu2qMGt`Qns!q<-qiw4((6J2mLU-kw!C+B3g36q{A@mW@)aji@V zX(e@I)aCn?2r<8xT21^Hbh3?AEmp20)6Aw4m%b+AQ97uht$q0#vuz#J@Ihx)I(x{% zyh<%|ej9qNcEM!R;y7tiYrZo6DEdZwg#(3FvXJ`>&<1zXC0py=b=qe+eFmyTo~%_d z@#<-o8;O^n=R}~K)hRAvZ`*}Zj*&iYC^SvJ_?J=_&gAKfFzzAY+%JJathB!Qw09Ic z^qG>oBZn@S#k14`zNgt@U4k!*#|zs{n0d@D3_$kdQNNT0^(n?0EPYE~D22yq<%(vs zss`LxPH+d=-x~Gl?0n2@ao!2hC8leK1lrS8vrU9qK=S@WAJ)5JaN5h5n-CRW z&kKJ#26n{eT8&_PuKjA2ZdDoT86kS4`X4s^0(L}rso<>KTxj_sCPqB-u0YVVJ_M5I z@u0g@Mi)GRy2PnTa6@mg@xiF zs)8Y^aw6Je7w(TeKf`N5^pg5S6WP{minUbxl_dX3E7bo-Tb zSE-D%sFx))Iq_KDbjjEd{6aP}N;TGg?q+cdUvTJKX;@it$O7MYh~$Q2Y=MmS3T9@( z-3_4D$9b&UJg7h$in2}PZDz-f-p=X95DA$VQ$d62b#H6Jq-yxA6IFK3zCe#a#pQ^@Zy-NEQuM%rqZc_kuwY&1o=wk|tn0#rr zals*DAJ$E{FGf*gv3t@|b3v6#U^b*!L_YbcV9gx0;)#`ssLZRE-WS){nS-htzfLg< z8(dUT-A|M{qI3n)gX)>i>>glEwD}!}fv;2s_`$D|)#q>__kV_Ni2PRIGi7&PUWf(_ zR8Jd?an``Zj=KDTDI`^AQNQhR0^|U?^;uIFA7WO{7>u`!Wv9V<+F-V)P=-8@11l<= z^IOQXF+sYt^goW~X473s6AmS*BWfS>!Yi71q33kVtEmxF0t3pAER&Qkv@P2y{rq%J z3oGzJ2g)SQ$HmO%i_H_x5S@zla>~6c;Q^gp;DLH{a9c{So>M%ao>t1e9N_`T|4dT- z>qzPIbn87Pho_8+;~{a zaHZPAIQQJiz#5ZnwHVb!B`H3k68J9z$!)ok8toH);iC*r9oq6$`A1+?+Fj6M8v*Sz zGLOMvA%9P%-CoW_{FFaDCGh^<{4=eU@`XU4_pGeY_vUqGoP!#LaeMHJ$o8iP(VWV|V3#5aF?2vli0~=0vkCk*CXJ9J$+4 zFCjEQ7?VInRi#mm>0gDD@G+1;`l>AX{XHGI+)$8hA?o~-Tds$P+zV22M=^5!P>mjO zx3|j-V)#SWd-9DD9pzSWZ4d0y3OnX&j$-&-;`OW;IxL2p0*+Nbc(U5Ro2A=4xTWU5 z6{@wyzBw52chgY8Zpz^h=90&iAK|bMsZEh8y{Nw!xPPzIl?7Kc>ZlHTsc0f-a+_uzMH(iiKd$S3^)`FO{NXsr&F{)IT;c&y#ZFuHXEmv06$3YP! zwBqm69HS>XBOoaR^BxFSy|Ye}lGVD=l`J6knP>>vQp=1UON-aLD(`d(T_0(&OMqva z5Tm(hIFs<+Dc@&4GqQSlGu#j`ODzrXtO?yE|d_knMbqHKG~?g0KTLg|I9h)us{3kPqk^2XcKU|bP1MF79Bhyy zrS<6Y8AYtlz(sf4g|~@0zm-x?#6axp-K*9*2^huA!yzd zBvhx(x9F6$Dxug9>1*7p4zb8ucZ8ILp_Z{)o-N<$x|5|~HI-|_xcAzzekQAWz&6sE zQGMXW+G@}yU4nZBMVd?I3LxmchC&*j@r2T|;eLov6jnagz%n^GLyL?-^DAgP&__&4 zKK^0J9lW|vZ{9j?dOo4=C|JyTs&QK_#8s9fmQf%SB3lVY?1i7iOOT$hU63wJ8#H?; zvucxsij=wt^<(@3BGOwAe^>^6R?1JXXOHB}79J%rL5}l>UEp*bbw+EdtKaWZ_zruP zHW1$p7LL6p`h>Rj#4tK$Ss%(1RyEe_jOIfzoPE9Gs1QSxR#==Gk=r5$rg9Y&_o|CR z)yn+mChnjJctT^Y4clERmwdMxHb{qi$-Zw*N=8z+(!#e=}i=`O!QT zS_Qw0_{ya+4|fdBF3pFnOdbya-zfm^BR*jF8^RFzFQ+%trqqxwu3tuaGf!?uG{bQX zVK{iFZiB*yG zu?KD=!l+A)Q&!Q|wi?=7RL>C_(v0ohT(=pf`yo%C%p@8GuOx#ir?Li4+)gu|2~UPu z*3h%X8U8c`fYuH;8=jst_P1Z+DYHNCR%?_%CLvj83%kiG=FnGt%0;0s>(0cU@Me_T z-oo|i_|Ex##>%p#tSm&p?79*or{bZ_j(3SUp^RJ=L)TWDJ}6esU4o-ibZN?gSi)O&|o5)A#ZP1%yD~a0Y&^d$99wU_TXfOmcP)uU&U5bv9TfbS`U4#}( z*)<)Kg?b3(Y)lYD!$}T6jxtVIdvFPsmMFg$g-btGNFJ`hG{NYZR%*Sh9a}q6wuWBT z+_8$tqihdC<2mg36=Tr2j+*VAY<(LcxE2CCmG=jYHko^cyhggfyBx9t6@v|u?dczl zP%H*LxX7;7)?}KU-|dTKM-N2;FolTi$5SV12*tXA``Z-w_WUskmHwP8X36QMgj@nh zxrYK<=%=Z#kJyH(@(yTik>XLS;?qZqw=_-Y6&sxgkQk4!s0#c5+cjJY#~G)fYrZ?l z>+ezgradp$2d$?&E?o+PAjdOSCQM=;U%(@isfm9Oj(;6RiquUhlEU}vFT{?Y2F=g$L+`P<>L`yy( zzrTUP-ZTH$Raoo0J{Ln0NVT~<#6yIoPe7&Xgx?@VsO_6PnDs~KvVtoyM=`RhPE_v#G{-zSD=g3z!@Q-Eq9ZF0TKl$hfI(|0mn#-}VtX zPLkL{N|-Q{VYD4T)ZA#BCe)XtP_ZBlA==X)GOdY)gxGAkQhx!>e)F1LgrDqw9fNm6 zi(k3(Sec0wAK|YOoW-6u_G@5C^5ic&IW9N19{#5}+q~bm?E*kKe{k&12EYY^Fj2~# z1n8`wQT#m}l{D$!oUk-VO+PJ%#O@EPG|4wDGm(KABt4ac)dyu@^_vUSAjC0+7i}i# z%sQ;CNA|_oEG|Ydy~bLT%;gzFif3+YZ4(16o_Z$dm}*m()-T?ptZ6A}#MJiAxk@`K zFtc9uSe*Zi63W@2{X}lVI=87R$I2>Is;W9au{Odk2~%Okt4~(rz|zXRT}kxo?UMCwD3w!obnb^)znq*69OX@N*x8sf?9DH`ZmY%tw z;X%xu&ydqrm98FIRbPon^UNUMH>+M)1mmo)O+#(AM(m)#E>3~!D9VDBD4IzZGs?Vp zG8L%X?7E6wAV1iW=2xgzo0+GGz!=2Qb*EDmS%)Fy{nw+<=6sPtegt_nC2|lIg9tx$ zHq3^#ij;W&>jd-t(z5y8mrWysw(8P)B4=y7(oAJh=3q1@#!HlxcH9#!#o9nmu1!-_ zBb7LHTNUR9_Ao4_`A4stRG=*$vVWE=9z?x+)HJ=nqKp*;IW^(fVXsYz`OZ`H4_V&= zNTg*tyVz4QE6g!LE;w;qXpj^6kBKHTviSmZ9K$1VkE`*_db#3~&K zPD$@i2R}<}s4%w*s?bGLy|Pw2qkb+>AAq6&K|pa*NX+1f4IdtQqQJ3hNxtZhZo8M zx^HBzfdvZBgxz4`83UOZCRI+ed$;GzMqh z@KSQdGQ(sE)$M#3vd7ThdQYO^b$Yls8Q~y+Au>cFX*-OFt67HmOxjDvB*{@JrN_kI zXv8J)pwG>;txUd7*$xyPs4dK`PRQLWLTDdBM7Ocq$8g6;3%de#q1#QPz#FW zKq3a0qAB>Ym9=fP#(m^wauf_|xTFW9&lXHYrlNr-CsfCP)EVK+^p`>I# zHI1DVE(DCR<8-If$HU!SGF8<$O zw*M!`cR0eoq<_BA3}2~%1|LkI{rdttC)K58Lcui3rAEM(8VC@fhJ}W~6dZ+WH zzous=KWPUhW!6xG)n4ZT)Nt&}@$%|s4OS4@_6^p-%a}E3Pc@MiO@8aFA_WYYtc!e+ zn&c+tn66Bk=H$~xESY=)X|YVQn@#3FAq=)k#A#cdmS4}?Cx&gg`YATv%Ec43pqC~Y z+?Y6#aHGv1?@7T{OcRQkOpEuL#>tt0VU-DmzqtxB491kVQe-Uan#|o)X_#!i0)1aq zbgL}_lbF7E_olp?BYUw=f7u+2@19D+WW%D^?n^rDT|K)qcQ7~e3KFZ>Dus}7Nq)eA z@ZL?`2Uz?Ri1Fr!kz(0U4%BiHG##dc3nWU+OQ=z697!lDI#2#%qdU*^$jUqUv2glb z?Ciwq%1e?KqCm?epWH0QKCKj~l5?A;o;>*`!MdfFtRmxXMa5!gY-_M#3*WB*Q(B%Z z^54x{%j3toS`&uWAs~rziGTxUu9ngGHEf238aja;0%XEOfugq?4UbXA%>0IyzeUeoMf+8goTj0!gH7UJNG3 zLFUdr5?4>=iqytWd{VZZ{Fz)FeD-<9llhmaODAWNPtv+3uat8Y3H=U{8f|HWs-z!D z9xgM3LBC0o#z)6k^w`I1h(?dJd|}Ac^YL(sba7X+uBkzDwlYC1io0|V-n}dNW_AKJ zJKsUq7EYr&jF`34@1EW4)noW%7p_)64!uw?5@8M|Z6T@L4U-M9hHc_n%CT$Xuf{^6 z(uHNQ><&KCG=UTh$x>?LuN!$%Z?%fv;7HXZ7juNKdA~k6bA){D^vUP=xR{a!EtiUy z2O$^HyWiB*`?F=F6V)U$b)~ciSp;=5SV$YqW{2Y5wr6V{MV?X`A+zi{GEceWSS1#( ztyBxAMZ>B3<^VLZ6wx9|c4OD^oqxisUt=BO)#T zpxd*U^lx1z1m*u_Ad7dIcctf2DpbyqvPF`E%L>ySeLxLyyWIZM89vKZ9s$3fW{NuP z$|<$S#39dYnfRNT9y^LGT9c&LI8AR8PBl^XUipjMgWF{i`#5kS#{LwC!eDOIf`QRD z1wG|PvOMgg29{!DqCAs6+QqD5n)%LnVTgl^I=mNr(|Uc!mDfhQaGHlUFKJ#+APWy` z23e4W-PVo?t2McUq3BBWEs<$Wwh9`09_H>ngP0zSM9k17`FgNDdFDj6D%yAYqnSKc zHI%EaU#9fQ(BN@0(I(IRV-^-1J{UIQnfypJ|p_NE3-lV^mI~ac}>=DNu8T)sJ zyvG3D3>E^tTV^abYeA5B#oD(0IRRVKWoH;TdCU}FK^?@yFYM zdj8UYXdqZ+o^D*He(syUddI7dI2XanZp;g-13*+wzKs<5E4)K+97T)H@Fuube$W5W zV@|eB`WsdU0e_JNWIsd(ycLM!9M_07!1yBTo*q=8SZr37+$ie6ohuD_r)K}4aaRgt zKehsdKg0pbIm)1LH?>H3DiG{|Q^Pe-R5M~&3*7cQ`46Ia9pR+~YNuM;)mAf1B?#Kg zRHs-9$Bk^cdR@XXIL$R|dERd50aqz}9ERb9#%2zz_ICTu39Esv>9Fj=cPpW?u1WM% z9ckB^{yV>Y*M=PK?!6@f`WOy~s^ZFsp^wlf_^f4N{0pFmNoRir>j@3o=@!ivYqg&t znJwHZIKl}|ms@11_RSd!xmIJs(OS|TdZsR8i%50TL(>cK;Gk-oIBmbY)o43M**rb6D6vy!oRM|G>z&uL z0eM?96yN*djv8_KzQr20m7z|M^|DNd&Pu`PX(>SMj)0~^wPD9AyWP~cz@XY z;T%Mq2tmg0@<|?e14rCZPHA#QZ)R*??C)aA=&(5Ay9~ufCCa<`zmHFs*eQG3-0!y% zFm*%5zDW5+nGiC-?E8zqJ>Ox0O%5VOmKcHbB{FqeGjDXx9Dc1?A(!F2(jL0tK9eCo zLnKwsZ%07;gK(82SOH3ZP8d)R;+k5~ZH1VJnki^s7Y~$T>uIT9yWYsN@_V zB)sy3zV<-T@93T7&++J$#mp%cl7P`Rw%=Bhh>4>E+Go;`Gg#PR1CXh1QHZL&u%t|lBP*# zZC)FS42ugAtWmKt4Z2kl@2E#sFND!>25$6xseo+<$iEv9P3gCu*Q)YYhz=IED_<;o z))TpXp-3IHxk63x1Q&idp$F(nqz@2*34iw4!S*G1tTHRKhHo9f&_6=Ws`}*kXUa`L z_lT)~{W5!Hv-&jnQHiHi-5L_@i1GL{G|-dfrm|+0P@cCGK~N1|>~sFGHzRi?T-UE9 z_u;c4tM}dz;id(bshvyndnIhJnTlvX(XHmhq$wRByku|7=bY=~3hCD-W@>s?C*u(> z`{hkc%6Fq6s7!E2nGh=PilW{W$D|q4Q;R}xWHf=MG&`%rh*S@mfx8%`7UB|U+kc2G z`X0YxdgISc1tGMo2B;YuS15#CH4_KMB(!R4#b2YWLloxyRVOaf9(uOS%R3;lIUN!? z>yg_beM&oGm36r=JSbpiF?rvOj@e{vqvdS964#uwF(@CW)SV;KP4a22dqWZ!X!zo= zKvmbw@tuD2kCFGbNP1VM*sEpHSYEkwCc)fGu zc6y&-npSUS(XKgn0z;@kbFlO}EFP9>=)G&0VhX($q3Z@=Irn1kMy|c0&Y7wc=i&2c zwo%poU{ql=D8DAA&4)^3E|y6-XQpks|SO$~#Fd+7Fsh#J&~mHHHLmqa(7`thyKzlU6^; zO4p*8crkjo5enZ`P@%&T>CK>e=9=lVdi25x1nx#4yYd%Z2h|)OZAjsZkbg!56raMZ zlW2~?o4|ZP;9(N{A@osw^KY!-=Kh*d?c+$@FMH_KI`UaO`8cPH%nM4Fo8ao7bpCc* zveTmS>UI4-C~%e+0dNU%oU$rgaKx=kq~sdIm{5!Zl1)!NYy-7x?euLHGhMWn=t zsFU9nptt^Z2nB7@^Hf_`6Q|;h;SRFBn5udR-SNUci&!V?H!LSy6k#jtRkRCV^d>&- zlsw&}xK#lvImTv*j|GU11;~yWHd9v7sOqzI-)3HuRQ=;^n>K|Av3Z z>Y`BcdS#g2@J5dOHkSDt+*a+V>Fqki9x7%=Noc(7s;&>K?NjaY>C_F#*V-$$-}s2p zG4hKhlRsywnhqFcmC6M zkS5NP4r?G3)}FktceVf!>2E{MZncNCsG%7uv1B(7rkA5Qt7UK&qp! ziaS`xbQ7~}-x^Co#o~g2QlIL0#6m(rTh!G3z}6R_$(`JP(>BSsLGzJ>4Z}| zauG&eI{EUP(@?^TPr3z1v3ffq!5!tIHJeGMTdcDwqyeAM7Pk59O&*taaLTSx-Hu*Z zUVhtp&j##+9yh6hAthUUAZ%sc!rEhP(zRfmG@#{yMb9=X%9CbVSa9@w4xF4_5Dfh~!yGLtkrv5Pr-nNI|c#p*|U7 zJ&MvUVbZto?h}t$L&id;?ssln})xIH3dPwnjG>{Op}DL-$qMat;d7U$}>7lBl}+?T`jc7kK8ovhOp-zc7^ z^}9@K0y zs*xb0tWTgqaDic4(0JxlGFXYFzQt;O+!&L_#nY+tHD3UhKd&e3ZpzM9O8Gcg8Yfkg zH*yrG0NcCj(d$+_DjoraolUrI`&>5vJ~fAh7K z@JMEwUZ=bb+o0X+w(o`f4Q*=T)&QkKO82Vn4XMOg(>-?+eN?_b@piY?`w@Ts)~||#0P#z{ICD}aJA5A`zN`=Q9-iJ} z*xGE^+G<#pHW^2lMe37BsI|wa8U|3M>X3y6GcWW*wo`a_;j2BQyiegkb*U3%&OGRIKV$@;BBto_g}$tEUc!AnLc-*A;d=VN8;tQGo>n_a1!QRBp+1bqAl|jhZ#nsu!#8u4G%*5^I z2fK=yv%8gv+5g;=jcCL8>Z)b_%b&>YWJ%^pW@YeRB(|eUAh%L6v@*h`DMa{?4Mdf0 zj*_QnKc737b;tMX=(r(?0Wb#@3uKWK=EaVUgmfG`+r|K=$< zHYDVE|1))f^KdWs_S&uN3*+O;i!54+dIn7#;&8c!BZ~D5e*f|c$yMBr>bF*3Y~U`S z9R=zs+t!CNaL7ri6YlEE5ush#2m4Pm3hR}EOJ>4PVGO0|R0=QL?b8LZ`Ofc6Q8Dk= z-90Ft0Bn)`GjALL7Ytxvk=wtfh16)>tYF}>hP z-bH2P1{L8BY~aF+HT!dJsmyk%{oO{oYy_QB8%a^SvehQ1?%kV1tJp}67EN9kTXy)c zdA`)7bAT1-Wn&*>QC8e|)s(AFn=V(nS@ZPlIjv2K zD67r0BEh1QUcLZgb7^}o%DTSp>PDZL(uINcRJ-XKj5bA|rQ(~~GyI^-o}M=OWmPUw zP?cd0eEV!kTyD#4f)tpwA9iywiKElx&le7AcMmXCr6+4>^zu8BMT?H!5ZQ;unwyS{ z?gTt^9WQie!KPL^cjik3R zcY@(*3ShMx{H1`t+UFjNj&qXh{8k`v9YRWTFHt9NFK^SPo6rM^4UI}KL58jqf7e~= zvzBMtpp+_ePf*gh_h=qiKU_PAoONH9lGDy?T~Kl^Su)k)0T&mtb)j;i-Nb6{LM4=?@o?&X#PdvMLSw7fHY&e>LMy^sQM~7dw>0jOp{5@EW?}x9F0v+p_p_U#(^bQ z#yOwvO8PW2$=EgtW4b!bGRdMI1Nu1DM3JhWw6T)faKtTAK3o%NyHP|eZ)=|+DJyyI zfx+f>@DRX0xOH+*B-1EEeh#anJT|N4P_eOxQG>d)?k_r~)U7>t&+xgweqrw|KE?pP zF{Y3`p#vaoP#Ahu99)V|Via2(i4aSkP<0n;sh*PlrK{+xg;iOp>rNS?#d|NY*X1!t z`Qx1_&S~S z$3S#H)(P0fmEq)wGSs0?kJFkuY(mcBpR_Z~Te3DJqBnDnX$%M-kw z86EbMgP%k|--L~aXp;3#z;k)v-$6*=zoAQz#ZkwmK#n_}rh70Mgusn?8F;I=TewfI z9g96=TEq-))^@T0EWI@O@&$;U;opJnq+Rck&5*Ya7HP!`3(2O16S|UaIn0jQb^83w z6P_g4F8z3LAp83hNn`A^`&##CGHbpam1e6BQswSz9^pYy22>_IxtEjLc;F`(@%g?W z%^^8wctJ1UT8HYVIVC!iw#Gcwuf5{mz(gqj5=y_b2BMU!b+~@IR*Y1`z}roCv!)ex z=g1nCYwOf^P3@1vIvSgc6VxB?S`le&E^A*^oo6QgwIQ&Mp2jfDb;`B$MDuM<*ZmD~ zX)RstGSQ+EeU(mWYA$-=rA|?%x*1q)T0l``JYGipWJV&_K@yrSHhOBNx z^q)||$@0kyCBU238H^Kn#*qmLMuOSr}HkA^F^-L=fY^xWC&W1mXrYUSEv+ZU&%lu5Mp}AANqO)Jq z9_P*L3%TU&Hz!X2qTi|;%QvA%|2tK4hW<9*|ORR0ynCk5xI3e8tW6QWL(tZqf@R^t~>XG(fYucRxFqj5%nn^F8%z6B* z2J4a+D<4Tm{{(HjL+W32Ovm%ZnJXu0yVQ|?ekBcyzCJ#24IF=T?~04^`8r1W+tub8mV_= z(df>knwtflP(gNXGaQ&HMQdI7(F4c`dTBc{i+;8@2MR{~5nld(K?YV35@9@P$Kt1q zD4X*Ucq#9c9*^cXcXZPmo0!7wEUw?^I!=&e{1aIDgR!ryEN}8#!QFRaTd^PaTD$}G z;dfkDL#K`Th6qL+c>@@CBmhr{mwhf@tp1~ElUHga{sC-5DPAE2K}{nw=|G zQFEfLL}jyo+{%W{^`3THTW(#O-V(XWgTE>kMdyXp^wK>MUO?sH=v3<>r@`XAkrVQx z+h;0&?SjXzvbgvL2Ot*6$hEoqRqk(b%Rw|1(? z7$7uXe5P8SwP8g1Lg@oPrxTxe)OVPq``iDElYbv^9dUcTo847AGtOaCH9nkt*9qAu zH}RXv?ZFGx6z8rSbx(okHxJjvF^}qQLFPt`{*ufR3H;10S`IuBhBzewukH3k!hOYx z9&cm|h$RuEHq-9*%=awwF)He;jxXf8fTCn?l9}B**m$>CK>FNp`U-t)u61-@6?;u#SU=SSU6ETsDLYYScG^=h-|oQ^)ua!X@MtT*Nr#Vp7_ zw7MLzoz_SxQYy}IV~`N2)W)+djg(N1PnWnP1N3~sn@&=@%JCDnIWj$y0CV--;eP|LRCcP|unG<1yq=P{AV zAx4_xbzGFoamtXWKXia!SAD_rZXK`Ejz`it=@rI#FdjBy5@!oVP$1r&wn^;1-kKxf zaiSKvn&Xw41o`kZp5_A8e2hD2yB^*^C10rxh~~bJB38&vaTHE0H%L4+_a1rv+066C zSDoV^O)tsI*+v?J{^kbrNpRturt9T!d3h*jkhX4A-?(0M-xjeKAS7LUvO^~k^cQj2 zQghvXsz6N)UbCjz&?ojm$Dfv@7`~J?0Zk($IIeO%P3`i?7bh*t7p?(Oqf%+Wk^F>X zZospoA7Azt2F_pf3n%sE+kaMlgeS=u%BZOx4C?E);B<}37*6o(I-VHFm5B7*h0v!* z-m|fN4iNlbVmd5;=QyDsuD$OM(~0T-v#WM?Hu4g-buh89vbRujGjlT|{XhD;RP{{< zR5i?hxy|Y8_T_WvYf-4$CFAbxW`(Gt+SZ*d%C_k;G{Q)3U3ip${=eg1tT)1Cf+f zpPI9jGg3ciUq;KlmOu;eNnM|V!HtM;l`dK8yGN%u8PQ`8sUkdA9mm-3Y1Ury6_NtptykK{M3nZW=}Fz zSKYN|rC*(m-dg^Y#i-^;W07797S$G@s0TiQXYLM(#B&5 zcWk>X#BXveuW7G7L1w0yI(p#4t1)opguBT%&;`SuIx{u1Dd?b%tI8cXykLJ}M&!0d zeybffW;}X^(%oMHgYp>KO3UHJ<8-@rp`kc`tCiWvZzBvjFT!uK#|1j;f?KRkG~?8? z?s-U9?bg!3z0Au?EfXV0*Ayhu#wKeLhN2aC6r6Z-9;CCc zt-rGj&SKsoVBpH)?xEhQ^Jr+IlUg15!vhut`u9ykM{t>v8$y7`n@uUqLfud&n-)nBXw{TB2*2OP*RMQIE^; ze&%Zvs0uv-Ew=ye>9;DB?K^9T zW1husmcbCnIY!%&IL`8%P5MBh=l*VkY?2R~5@qq{5uS|Yuvk7X^(FpBu56gh^FhUI zV;SZjtNaCRGo|JASUjlz)HDsZ~Cwl=G)^;IG z%n6g!4VKjxDyToF4ua;2RSYurbi^LL)XbHnrV_5aqApsbEKlVMwbY(J$!yQ8!mhP6 z>0ocM!5&7>gBI1Af=ih|Xb|>~uzo|bJ{nsi6%eZaMD0G7Ye~FG&nJ@qz$9t>;j>1% zg1c9hmeGq%nC^df+m&p-EfF$~wE%qsaOzg3~8)C=cb-We<2%YRhK79vLu$vDY9@}lkj&D=ArdeOEVW#_Q%_= z?97%sFTozv@d=8mg99{BqntV=qUjCOWWCqk5EUf+h*SE5sQO2#-{4jz9Jpd`6OwjK z!aoB+-;i=2C|oD<_a_Y$Q~D2jKIMi~;(>&uSODI9Win(1#GYF5QkLV!-nx@jH+9Rr zo`0ccjPW4Zq9H)-{w(3bO7iuoL&ep#bYI7`V&Hv)jSxPDsc}T#!Clv;rlSq3g>-n2 z?rzX%X0CX$_s7SX@0zIDP2sHRU&16#mEx_H8b@qYYxa`AQdivqd%-cuU%+v^gs@f- z^&XNtQ#K@kL}k4ffMHqais5&x;nCNd5`WCTtAfo>#)|?|2-V-gbDF}oTNcgLS0bCgShefiWq<={<_hY={jP>goOuB`)UIX>6cx=GH_;aJPyxp1= z?ngJ+-WZ4(t@RgWeuc{3sJDv>DeFDr_H;z~i7=@|2_p8sUJ>Ns|5E%>dAU*2qXGdL zQ3C=^9U&ZoO^5{jufkOl z0vC$Ki5W>zM^CI)K*y%Kwkt|_d1WYkt|h z-(`1dN`{$ovd3rli|>@@)c!D0UK zJxb=g3J!zx?yPBKzRVQE(O#x_;&4A{6+Sr_* zrJB~OMU(+3cX!%(ouTm{t(OK|$eXv#c6naLsap643On(4vMgtvOJDCZ*ZvaY^YeOP zTg$wjIzpSiu|1U!?O^F;$iK;3&YW?5K6b8@^-pts259FR^Nnq5Jfu#cn>D?ac6&eg zpqcxx@&4LQ1*jkZ8o4aA3_Ljg%3E-EI@GkLA!pL9*QE@urJl=H^*XGssM^ap+i>Vi zc49b5BE$7{$C`gfv8LA2^_(B*(sz19+U5dm`%4&>(+~QnYZaM%YkT>EFWGW#gke@` z^%^3oQ{9h}b9pwC_-;S~>r&1ZGOx%|SWJrT6m`aF%P{3sX-%8ro}Oiyche6q{=%fQ z$Ky9w%(15^{7G`dP)UONL)X$V;?Bps1(tl=p8lw3JE||%pzev|wQ=gz?A=?9 zXSThj#`3vQ&gelx78!?}WnJINSK6K1CxcHl+m((YbGpP){)(g=&AA$jfKRSFcRg7; zacg?#Y1vuRWRUalEK2HK*W4%i)G_TauZ2sZx`xw)J^8dk{|{NUo*1l(PJ)@5G7&n> zoSLU+0Z(e7Z1!r+zRb)Z8+d%j(+amp)Wdq5Z`?y1ubF)HtNX2b=%f;#%=^N&UM24K zz1la*gKv|!q;X@=f?vDABw@-2rn%Z-^q{skYsZ(UpkWg&0u3aHFRe|BC?X6U%8DAC5SnyHxXIM@LcJ){{A_bWL}6tzh|RnDR!!sBdc zEtF!g1Ch?CW?+&rP5PR+W#e_1aVNEpY@rFamO14lkGsW?CUHCONQi7@VFu9%!TAR zbUxBlld6w8xVl@HyE988k5?e$lBSUs}y(kP!7`j z8y<#&2fmSOXfc|$%1nZA9%-MQ+SSCcJ|jK~Db1`Aw zQG8T3#LJw%K|Y#!99DoFv{>Z;l3R%cUEVxI_0VUkpg=veRYbkQ%pC+f3Qe9vkoYGK z*WhZ+zVRr*t#QzuIw5tnZU|Ms0^7)tA0UQAZVZerywAG>14nzn7ySwO7Qa&AK4 z-E7C*thkK^r%x|q6HQ8SPjtO5F4!~!Nub6FXQM#HVUO0>kc7Q)tL8G@S&6y0d(c{A z&(XIx0w?#86;_L8Psb))VXc9})-$ESNJG;ipAHJTvwJ{y7#&LmI{=@I0&&d~_M}rv-TRKRh6HMqgCBhJ z^A%~$mWDgdIJA(C28z;=uJjY^GG7#~PK2)Rcs9&qjJ%Bg~&@n`O z=gI_o{y`I$rBm^?_TJd(2ojU-?8yf$k2e%lgJFF{fv|dkLm}sHm_iG7(RCG6SI9gUxm=pSUc)@VZC6F2G|(=xrg^?n@^C zL}3ZMu0r|pPwG&D_MV6}>=FjfF*FOUlf(})zu}h#6`2zK`(mjB`|n7?pzPvyD>b##yAuKsLd_sRS(%HATUEsMn`MqCp6R33-#72yEKk}RdpVT z0(&wGX=*yOxO0C`>%kv&<4t<$rWLoKt1CN77LbmmV|gQtlr%upE?5c^NwZw=v0y8%V#zHwFAh&2?@uBwnE;zyF5U-b9+p&vlmSaMQjJt% zMN6)LQB)0}l({ymxDBu8bCx;slA3Z$raJO8qUIl*eqPMHu4q-mC*1duA?%`#sFB~* z##jW(85n4n^$j6^MZr72IWh_P3Rr2LcUij^zEoOXxP-01`5EZ!I2`jVDyNh zi~xSbWsJ_(ah8^>pF)D8L?5LZk0>mUz*+5nql5-A6cTB8KL=qTo6{uJm=~S=?o&eK ziKPUbgnxqXI(ZpX@N=rKQALZA&kFFQ-QltAQcGO*)a4SG%FoWrfKXu`uml`m5^7i^ zl2JZJE`wI6O)<>TiY~4IomdJn-+v$G^425X(IyhD+yx(EfbB=siC?ei=7I(p^-DV9oaaxxM^)mWMXldEN`pM%ZD4O29quZ5q~xO3 zcu#DDok03pI8}3m_`6ar9Bd9;bD^{O&8Y$v7m9M&-C@X%ywwZ;afcJ)I|+d6qCA>B zTQfQHo(I=d8ACHN^;;`xjRig$Ew#*O#MD=Seq@PoceG6k@_L&nCo^&TJxSUhiPdqL zGM6Du`T1pSk7@4H0m?NSY|BO04hMFmGbkDb?z%LW{#~4@2Hc!;SMxB{4l+3GJg=HX zI4h(OQ!LVnFr_ef(#w*=y%Mun8|vVN&8qGF_EW*s+da^YrXg9TQd9;vRzLv11(#E| zC2ET+oT+T_=8g&hDg2>m#b2@Lj!SVdu1hof(6%M6apx6thK{-9NNTuu&|IOr_#dLq zr|1u0P@cq~-4Sy3QS!58dCFo?Tn@j3qX+;%(hVCK2 z;7;E|$2Rn?$8ao@_Kn$@R@UP>M0AI;oKP07jlpmXTi6G!o6bTGtA*b-Em@vH!7+e= zVoGe;;aS%o-(Ywv69>q)^82~_NINzJ61EruKXL<$)1)d3O`?re-Z%;mnA57|@)Uhw zWzRC({B17JG<62v2wvq6I1^ZANc`AtuJ%a0C9hRTEf66gNGJ2Nuq80PK-&D_+lt`Z zut2H-8ybq^JEKyOBv&~s?>Z@LTnxPXf*ewy_m36=9F7p8i%S7+4wV5pQY`~l!caSO zWF6GK0N50G%kYu<@DcmaA$H;*cjO^<G3_)|C`e)C|(qgQ*c^aJwA+-i#s)BuyB;wRX}yKSkMDpd0(;ue^48Ig;QaMCfYz^djMG!xS= zVhSk{nWcF1Wjt*QMylmbK}CTry`7wuNmqAtdfws3m7yh>QIM(xdnO!QigMcf>ZeDnu+So#T z90xjauHt^@mVMxg^%&J>+8sk$g% zSTkA^?lU2bVapj&M#gy6OW}^jd_-c<1!Bl0m{Z}{&| zfS!NdV9$fP<`1?O0=rtO>@k1AToSOcq;XsK8K11EXZvh9wA-q%tRHieSQ+^Emth<78o_;DNW?9+H;P>GEU(w{%5)3Alyjp-VFQqV!sI!-GO zZN%M1SS}&froh|1%gjG!-uioELXM5j_t-|4x0t{Ry~9IQcOG%`qHQ z)R4Z|aZ=p<`XQ1dq-o;~{TeatV&K!P%R4;Av?-Y99wuygF^62ng{k7PrH1OAIw*4t z(CF3AeY%$pit7|~TGv7CP$hM4_Tf=1@Um@L=BN%2gdf!tJm8ylvaRD&jDqsGZMz|(8(Y2`(FD%jfML=WFhL3v$>BpoIsdLKt*pO^zI~euwukGecPkWwF zF1)t0#lSaQLO{mUGKGm-n;{NjP^cqpo=>ihidf9ixZ#-Gkgn`Un4NWtV+aQ{yj@`% zNrkD;MwRj8?2q~DZBRZVco(cSPl1ClO#b`(jGj;GEaZ|ppA|=RLI?E=)o;>?1=vp= z7bv7=u_mS3ysiniVY;qd*!>Pcxfu`7M58=S^3`z2CB2*SGV+$4pV?Ne=xxaxFYwU~ z+Dkz}kTRXu6Zz==l|%mEhU$;!@<43BVW{=E+RMx&UM&YvSa`Ymks9xc)%?QZRdmPF zdkj`uG1KHC$C&}AzRV2n%j#M(R>TwQ#6JRv0HtT3T|vxT!b*EmIn{*e72VA3+eRLM1YtGuwdEHK@Sc=ht)=C$rV}*Zn;@qRm2$SS^cZ^<0aX z9mdk7-gOYRSfJtrM^_!a%|zp8kITbhAh*#Bk7*Hpr_VN;!x-jLbfO&6qJo?GM1z>U z@AP0L%k+qg`z&%xMOBe@2=7jLOo^eKo3Zm010mR8LWT8dVc-3H1+MY0T%x6zRnv+}>*9_U zAwC$oG$(PbNVeZ{uaX+aBEADM6Mas!(P($mzm`asF{X~K z8MQu37a!}h8So&tGQELV#iFTRrW8@PFQic2Bv^-7@}QOi9&v1EA0c}Hy_v)80`&7e zcBuBC)5q6~)4^!bOs+`IOw-Alp_rt{HfX0d3g1D~!N+J)f8iPN4YViZRJMDpIKC*J zCRr6XRt+*vyjUOK?9WH675C&Gu&_D0^Jbed^X4YRXOZz{-sAxHj&1ROot=`}kd{88 z|109!w+5Sv0097&f&T}H`>(3wKeOCQf8+)k5O9Sp#(S=%^J%2u6(IXTEARwFsf2{W z2LduBWlbUEOmIMYM*`q?3K8h(P|OD%>n_q;C*3bnS4z!@){0oe9N}oWn+D1fS1hAP zHVdtMXQ27i9|vv^n{OsE>|(kti{H7k@TB)9I#{1KzKs_*v;ryR7Ky-v%tqYu8?V7;A5Jix{K%^r+S#jhl%_T-Ptq%Nohco#yCVOeUABKUe-)d*q;&^jm9U5Y8t$(Xf9Iy`aU0x;bP#j zcl}&)bF&yi5mOd~GnAgvmg#AKCYmMGbB%l2IvD_Bf-r^vOHM#6AZ9@WhzZUlm?*)N zA@u2<>$`1c-m*HA6?2?<3#7K6R4m#_-~DftP}XOwk+MIQ#{Kax|FOs{5%R^3O02hn3>5LdMbk zHFZ&Q>+}I?BSAw$`vKQG=$En~-qnzJ}DWxwfeb z;zz54Zg|YJ+MKz!>|BHo6!Y1^XSEZP1ddKMP0+_grkGG z_-DY{MWUp0<{;{azjz>Grw-Iqtz`4h!yLBnqy#45dgeGD6$NK&+o$01^kFn9#2Umb z-}PAeO=X{k^)V#h3<70DF);E``j9lY`DKxeqtR1|?j_jDvraUBn12~HgrbgY$oQ+G z>O*k09fA(mrLQ6fRRvHf!U+lWoB@~gr_$eFqT1-*7O=Z^hfcVWgFaAe1izsLTu z5AlEU`@)9S)`mvb|8)5_a{X}q0ti0Bm61fdXL{(eIEd)GBn|{u>r*8bjjK|Zk#vOekZ|t_&nSoKFRMq@xs}nRSM)vARp723)<?&JRe4Yuf?A(_xaZ*#ElpDp7M~ zxu0c|{lJppKQG z@NheTsmi>-nycYeJeToO_9rHjbZcfq=Yd5r=| zQGydcz8Pl3%qLR?I6hoAAw-K8BMRgjMb!}zq<-f}VH$#tgQ>$%39=3@!_;Ew1a;Ie z!^nA@FN}poSZ6?d4QO*thPESHVY@cCJtP0C8DLcCx_S*{ggY(}mJX_VqZe9V!_sYE7d2{5iNv7RBC^vI{fCuF6RCm-{yuzD9&~76LTN_X>vrB_o*B;LoWyng-rjGJ z-~DP3gfx{3`B9|#JGKxjzmcT1LV~@hI89Khs5F%Aa|usWro?Eb5a@9*M#aWmCbcSb zTE}Z_oF*h~+*;|Q#U7I#l1OEyn`Fpme&4`q(J@M_Ke}ZZRv2h~G<&tpTo%;p6U!DU z&|^0z7-sIRD@zeEhDMa87_+k-9kcNNcGJe8ZBHa@LG z-R+Xe?^h(97&s9rs9sPgBy3{Z{0%}T6h!Op{mItflEEm|&MP&p?hL$Hi?QAK&@K(K zTa7`W8iuORncqxRv9pWJDke8KC_IK~n2!E1r;6ZVN1`xot@`Jg=-c-l2k?Uk?lYNt zff!5nP6-d#P6M}u@@|0VwLej4 z9H7#-44}s)*@LNYisgS7&s;Lh^$O5OM{DW~yAb7`H%__Pu#dSW_Ket5cZs&7sjr|( z#^+S|4TdB-Q6+#=C3FJ-#<@eftue($k+?L3)d}?vwgtQBz(w*eLZLwxCUns%0whQV z|NC&+W>K7N@sF#K{^68k{a@nz{};1J{wJU1U`_YaFThlQ*8&?U3IlF~gx^gOl(JHJ zZQi7;qL$lWZlY|gRvmi8bAEH92k;LeVQQ9v87ztm@{~&Naaxa)o|k{?6mc{=nV+Y# zoo2F~+&(^f#pMBYhRh@%c6^Y#+bjv}ga*OE_;^AwYBJkGu`QqCBRDryj=DT)%T`9u zn$7bJc8~+w{6@=TWgw&Jx05@W)bFz$Yg8PhS!Wt``Po;p=*G{oe{=zkb1+9;J~RWY zM>FTKf!=furM2_iRvj*v)}Bf(t0$(Iczq_46KL!tW)xjd7Eu`khPuwZyhPX0>!50^ zyqrumtJieeXMdbK7*INVi`m+`g-Pcc;or$OC0`$6e(qJE414eOMBicJFZT}6GK4qG zL>eybng=U9r{TbEpj6@@Gc9k~rMtsP(c?SLj~BL$s2dKz)Ig)e6j~a?86nzjHoMAIw;D7k4$}i9%d`g!rn^9N zT5Z7=0UK6NbgEH2Gy#i-$)IA~z-AscrV7Kf>-r6p>VAc+>z(53N|@q>8FW-U{Wq|h zHHyV;pDwZ~tM$f+nT4Jh%3YhD9&-^YQj&kt`@?|zA2_rH(Ea?=|M2;b;pClzYhi)C z#4>}u0mC@jmUGHv^;yK4t?>v$;oI%e5>ZZyUa4Cx8=L2v`37_Ne|-`z2BE2(LsXy^ z^h}jPU(d2fI}z^lpUiRUjq?d#@ENW7KA{h(spQ=A6VlxvjGPVSp5*4GEBv~{gZbn> zS>tOx1&k2fI-A9x`^J#`GDvT z2JHpEIJXYXya>y@co%*$3w-MwDC0W*`t8K_Tau(bAXiJ`noJ~5N(j+2-1t4L>7!C~ zVhE8Ic?xdV4LimhSIm2hkcd72!J!_v{#_J3vqI(54sVekH{a55MRz3Gz#G1lBd4S{ z?E8iM6?K>)s_~YDcZDHn@%vxn8!ecQ7xhnk!}!U1u>H^X{XgQ|f5o_e#DQ)9vbvK=Yh?$}xnf9PYqn$Mehbiya*ck?Z~>eT(KGr!icBY?vqDG~Nd*&!ZoTe$ zEffzuoU1xMo*9E=dH=Lkb}Ejr?S$*n!j6lw2Y16M1@F9^G#BjY>f44omhbuz1Jw5# z+;iL|eB=Rs4((M&R4A6iQs#RO`772H%6T!mS$cOJ=vwkrOn(bD`ALNt)&0tPMOM$E zMifX_`*AI1zxk+=N)@Egu!aO_N?cV7+E2E8KJUd|q$ad%i@rD76TB60uI4*5wYO=u-Q)-E!b?AR8?Nb|9S}ruxPo*BEuvHg z8KzXmLZ9h4+B1bCQ!LK8_)$6c)65+GnQ@AUx_$GuXnM~YH|ss(Y2mCOaH;TNEy8p( z4g)$IrIce#1hEAk^sX=Netz;dD8(*N$=APiUprh}lg0f|nfgHi0C4{A;PY5C2&H zzEqi#X$hukG8RxH?uE{(5|=W=WRpK4vlndK z_=b01CNdba0_CXGy~EVm2~W)&Ix}XYI!Z*;^P=b(+Lv2#SEQho$oy%PXGRSN*DihvM*(MTgq`Oa{yPnPt7q9j_Ap%`-(jXh7i|Zy2rHR~?w3jIP~|OO;5T zPJLp*m>92XQRaA+!iWNL8y_eK({_^{Xj(;KE>IXq*8X5DAPD0F1=Lo)ldh97SF+<| z@d;!D6QEpj-)=(KZP;w%$xho6BO|9g8;}WedDLB1QT;sBs99h0tK2cPtnG=kM~NYz z_H_RM2w5`Mx)c$w);6*!E95>UKL6djuy0l~6L_%L`%eg1N9~b(=K|qj2B&hYa}B{D zSMX^ zcKuVrWygjr_YiCD=tL&U5Q)EML)M$zdP`69vpr+^sIPq1ie(2n^-^}TmHybN_i+ zfv#bg(%aeJgI&ujaGv=N_|rrwk=P04okcfa6>bJw(FWwv&08Ab`Py;waeUqHyd+m) zu`hTEnDl$i^}WxIf!hbc@4?{@=<-E7@CAzUMVjJ;j2r*mGkyt;+t=m`xR&?|klz#M zGfg28ko;wiTLf8Wi0H-`Y}iOaPc^=%&=Qkm(_m2eYc4D1@;h%Pgu?0z8xDIjJh?NW zp<|CWd2SfK?~;DPBB_H}c@Ec3)vXJ)AE$0<)0pgoIiN(Qz=pKTPPx?`Mhby;|0I`e+v5deF{9ZH8AS)V~oH>X$N=jjb zx%?$!9Q^tKL1GE9rfX!W6j7`RZe~`KfGbLbT(VjwKZw6P54oxUnMZmw)Qc?t-MqLv zB#HKc`-ZFg!gG!PC=WkId9lCb-w)S;!9ug(KS7rIC&+UA&%KAzhelT~Exeo75K znH;VOSXe^ZQm>&3cYqEmp3+Iu6bh_;`HDk;aM+EOx>+vG^@y+P1OZeIJK#5k2#pp% zaHsv8?q;sPwlcHRb-&*}_sRWnSscf9XF9ryr{NcXPMYzH1~h6N!3%f0dDfslLI8k-rdNsCDL`)E4dv zl;PpOm{7U`r@s5t@O0;5!TKSA#2 z*whTx%kqBh*2j>SY1?DCrsP5lE(GVjexaaKn`rdbah-h?EikDV&$G^`Y-ht#Yd%3n zt}SYy4gUV37yj>uUww~ z{l*2^AyX+S$gpLU>52YEibNSKyBj|93)|Tktc8IT=W33V33YH&=CR zirADUH97PBW(ixxM_pfNA|Y zP+T>qCxbk*E;5E!#!BjkwyA%qYg>hbtJ1k?$Ku7#c0O=aNp9wjPyTTLd#Z4OVv} zSkKlTa9I^J(M>5;n;E1S(G~j|dd2-d+eChpC3k-X`BbxcYZ^Lz%2=?GSz9|)^mM6b zM>tdW#51Q)`*bTfWV$v@cqw zXCxx2kT+aU&V&V5us1a6s~6d6m6J>%oJ^^?^%UWGttK`Jo&99(??H7*+dSe{>)KWl z@?N7wEWH$-Lq=rk%re-!joH_q} zg(=p6wpUs4_?q7IdiHo^sAEDG>HC>Z5|U68NI?$@+{qlAFJ=6YivY9#qXg(N0Q8P@8sNv2M$@!v@)J;r?lN>KUwx7s! zR}OK^N83F!_BU7Rx{>M0?CwPeC2seg=?T=sN5@k?$weP(7c&0su#bxhdY77zr(8jM z9HsGeV_Bv?iT2?7tX)-bvqoV_bu4xK)Z4>~i#??tt>u}o!bXH|Y7{N)lh9 zH9EPmJ!MzO*S$((@?e5i*H`_?R_a=RZ$d*h0C;ut)!Cy;Vz;tZXCR}WTMgb;w>u@F zpk2tbh?`f34&_v;RmfhJEtj^o_%?=jbE1VRO*aIwqWpB~h}p;Z-St;wP>p({C-mSD zwd~@{6>P@g9L**atcmOO1O#4ns#SJt5Zh8XD2%AOr=KT>HkrAYZK}|w*CTJ6pOo1s zYDJknf7T3+jy_FExv{D7Ov6N|fh$0*rHD6)ah&6cBkWV;g5R05oR<{OtmYOXK6qSJLx!>gP~?mT3dCPLz=*? z$1Wh?K`^%__T+hj|84R-m0Id4mUc<0sh9K=mrTE&nx78LE)(1!6AJ$K%29cV?!XED z=|Zcwv530qy6<--tHz`|=PCtfO_~r{95v?|MC+P-)?u?B&GnrU8Nt1(-LQkTD8Vn`##_$YNCjVA-Q!6*kdM(94#-p-nZ#2HqMj?C27CV zPa=5Fax6UNhL99MAL=nalC3chP6Z5VIJ1t%J?+hRB-;?{#(04(G2)a}^U)P+62S^@ zUBlsIRA1~B!~B@xw*HaasG-Y~#@nO^J_2Mbho~5r`A%e?NnY$xht?Rn4NGH~O}3{$ z^P(DK$}dvqAul&gLi>k6B@AX{Lny!G#SQJ7G&oLTm%pz6js>`aiW{4{=z4m11rM*y z?r3bCn_S^bAG>Q~zV%wTk(NvfELNKzhTLn_(GZ1u*uN;J%~+KNcy^IFn0yx^k!9nfUS_xF!M|@g-A(`(@IzHWHLDyv`yCYK7AHYNpeWW6AK;NT?1DmICrul1u zDC>rWYeicvK5F#eieAYz?T86Hua#+@Zh@Ci^hrJ0*cE|R{+%XGx7&+8vE7ZH)V%Kj zw-U;vJPTwh!)k(ZYYc(AOgJB(@@B&1A@s5c?ss)9INg{VvcT}-a{}uZS-=b~;Vnax zR)wxmq5f5l-F(93mTC8CT{Y)k?LD(WWL8YF)p+AVqY7)@&3zSMX`rIbkssbpPYrBJMBlPMyPwBXj$cb{= z#yQB<41zbur}f?g+>`hHt8dw;GXP>ZXDCDDQ}eIS)bZT1HKVFD>o?QtsmT^ zbQzT41St(<{x?xo)CYz09LA0lf}01Xo7^a{xyziyi#g5RY+1J0AXDzM_qXhh$M!S4 z0@GsH>ATN^54ex~!gIDpMQFXy<@^^}Q}xQLUd!8my=v~1^)4%=ACJmCs}OBHQ>eNA z)otsKqLOf9V|nM&xtT!6%V29$ZS6ZCT&bf|Ek%bK$MqZaBn^V(FJMNHqeeQ3{bb}E zhov(5iA^KqKSSCo`-%KzH?7$zf|vQdUWS!8mWzCosvbJEP|?r6Bk#T&MUl}ikk}ks z6R*B53j1~OPox?yBv*n&twO{`^?20_qQgaQn?EDZ45cj&GYjMTw|#dL%vtBmT6#GZ)p{3k_i;LjZv0|Hnw$^^6)Rhqj* ztr~fVMvE+x@@YRkeSCVoa82N*=?hhe%kFCFOh;*E2od1x!b;$K`NxTdryPc@MArGW zJ9ZMS$(kTGd{MXy;+t7wn<$I21vMBrbd|~R;SZUR zEykolSo=F7N`+U!I(6*xfH$q8t;q&{DF%MA2K-`m{PK19*WbHIU|(zZ5;)5Pl0=7hH@wX-d1DnQDSELU{-o#TF8&U!L|r= z6(Q|$2CE3Ll?v8~a10*PUAQ8$aYfK0W012B(4btZZEzV;x(3Y%7-W#T5Mx%k1v4@QV=cVhyVP538_k_u8lWgvBeXvM26bQrk+ zDLBI}Q=T!%@kEPS)Hg`S60dwCQ>(Dcm(Lzzd$jSE==Mi^#5M77MqP4aERowcbODPi zZ*&*x9=g6sb+N`bg2db zrm57bb0*^vLYGJ&v8z$6w7noHNyIV2&mY-=Zr3wT5!IrSiZXdWQJ?6l-S~qPHo@y+ zT_|g-#3}o+@hs;kxp~XmKXs)?#A#TyEK^glu!fI5DwQi9C|_wfACPeaxe0#!#+NUkwYU#M{veuIjS@&^ZYfBtOWMqNI;%YAaom4EkTdVg~sel@?@ zeT15&**}8N$Af!lpp8*v^4JXdIx>VFzx40MX78E(`NXL-f^#V-Gjp?-mvnLown;xP zs1lBwU?=0;43pml7#-kA%AL=GW11~}K%`wdwTW9m`wDeD$iN%Xo`l$S#@$*}e+F?P}jTTm+ne1Uur3vZk z;L&=FEuJ8je{!#j>4KGvNV>y#nlN_n;0Xu)jU-Zh)cYqx|}x7oFn!P>P{dajXN+fVF8aLU4{ET>#b!g5B8kt3;eE*-Bf zsoinuc;xae(@vce9zq5)gVh^^(HoB@IKn2RVH?n}6semI-*X)tAuELX6k~0{X8mir zXcWf?Zb}?0EQTA4wX(;to){XWJi^muPSZy{A~dx~9Q`!-v4{$`#v$?mZ$W2kwz*zZ zSsc^YF`vaC-$)jsAY+sHKOW85)6E_?Yyv_=1t{Pd5Hd$Maq zPe)3Tx3p3ZsgktrC+y<2rR=Mga?kQq&vH5RFZzK*9klYYjBDEJYl9vyU{7(|941f1 zi0{D9@2oAqfn)I!DcY|@ys_hPuII?!wSChv_F2ts=PQtkjI*Ua#GuEs=LHcd5DCzT zFOGsO^%F{|Sj1|$@v|Lv@35IbHX&*;tVT zrk&0ew~k?j7+9)jj4D@m z0*n;y9u)inLH#niYj6`vXQ4Q7-Wu;ld~8}LhEOYBJP{*#=wX*Kj4WQt5KC}x4wM65 z%zJ=gOson+8@A)sKO}b4WX6r}HFKA&TFn!aDJZ$DgHq+*{V4VxC%zZ>(DI4wo!9tM zHN>LjO2tf!3^bTSwgg~9a;O;~-%x5%Ic8W}X+ZMUvdFBLf0O6>+CCKYUN9Wr4U2eg z*Vu72GbdD;4Zt-IaA^7+6vUQBl)+~d8h4p4K4w%f^oD|`T+0~ri3iV;9~HmFyHm8X z<89qX-lk1-ZX4nh7R%lWFf!iCDc3gMsB1=DdH5Dj4NK$wV_-D+?8F4=`6ThDfFGs2bY`>B?kNP1!eYZfv}2sCo*WI6yb4Di?`YbG_+Bw&1pvr(Y6oUzi#YB~77C}r;7!H&Zay$L66ul>TKnZ3ragk)Gy_Iz*u#oRrviH2dui1w-8QI| zCX(bVw!fOpE#x66k0?a3nyi(J4AXO}MYh$*VJ6YmaA3TA|FejTQ^&M-v$Bbeuwuhq z(UL{_{FRT1%6s9kXCH|-8?$dQkmGQuWvFH9GgdnJm%J>MG=(Wuj=Q|%u5-yq9@O=# z-um|Sn_U?S<$ZhI=?Syo3azajI-7Q9$!@9%l#>bdXbMIN%*26hs>n(m1HrDv#J#TV z3+iYCg*u0;e00m9v(DfKLWA(1dc6Zu;Ze{Nu9Et)J;uQ^58I;U>y3xs^bY7Qle;Og z>urVmkVh^>bz^njHP~ip+~-*^(uung2DOj643Q5$77yk1?m9WSRS>*ISP!;|%>f9X z0JNF!kSV-EC;nXwdz&&~6!`Y(*1BsVQU;+%2MPxn2m4&M2$D>dP5L#by@(g(poDPjq+};Nd zb*90GX9}ttC!}!)b47lviIT-70u?n~83ruN@6_{U=~V#e;ih2owlm1yB>|U>KSz*+ z1%c7;`nt*OQ%ylO7(LW_g`^o~q#}O9|7*E_gnRfY`UkzaKj`KApJ&R{9W9(qRm>gj zT+IHp?63U~)#lM}VRTRJJQWH8D7AVTT7@BOAVS5!IfcF=*@(Dxmw7sxP8V+Jo&JgA z8)fr?-{u8zWWE&p>BUtDZ1uN>$BsvS)34dgnXixU=WYPd8j-}IO$sV~pKJ?D^1R9SG=Z*)@E3);|Va!;#AO15;!a#0u9 zhN%W!N99Vnq+8F2FW2bUMZ!%x#`CH;YdM7d{xrR&wCeBMiq>kHNdDCGTPr%O#(mby zB-}#-a92>e=IwE7w=`NqyoU1lT5YasG(9VaB^wqAPY#L|ROr_eU+Hh>`vHZIi2f?x z!(2&*QL0m6Pb8n+{giLdAaKX)rHs3*mu+<2=|JPGO7$Ad>e0|uH}A%>7KGNY#I}6w z&t|pJI_kwyY);b1g5FLOOr3fyZe97Ik1ITMtRU6dgD+M%=YFU+GAX!otZqt6qSM<| z?y{nbi2{nCP@!S={SaGuCOmi3T6V79^7}IfoUO|{DvfZEsjhZ?QQ%d0hKL6DzPBt< z;xHP%X(9}%iVAmoZAB7|<+Gz;lqJ1o;~x}5`O*LFi-3)n>pqWi_p$b>+Judd-mVj zHvii_cWZfgt1NYY=XhODW~b{IATiC6&=3*?(^N4^^qIp5iY4Tc`1?Z%GYPPy8!!PL zXwrmFcxmNoX+vL`55JX3Mfn#LJn^<#Zd&F-x3~-!*EGo==6h^AT}@?6GX34bhv#!z z-oE}wxnG|SG3)WVK?Fz~()7)WoH4x*vF?E}K!eM8@dCiZStRra`2G^+PoE|G5|`_N zLb$QoC!s6e#m0f!@pI6q@i{-fa%(ufbLR95CpbV9a(4%!+e@I!^q&ouBQYEsAtvl< zV3gg3HsF98kI=t*J){tjBBGVmFcQeXpaAJNeNwbPWoM}v% zGR7t<2Y@VElLf;#JPROo`G-Ln2HqfYj8#+*K-rX^$lrp9o#1X*Vw_0ql~EXnytMGp zJlYxx$f$~fMKr!hfrgy41j8)ywj|9UG6JaQOrz^F@XcJO4{FqC?x`Y0LR`E_xR0BE ze!d*6u@I>bB@qOS;>Mn54KJd#In$^?;4Sd!$i2zX)Jyv~GTAcI0IC&Rh9E|q-T#b3m$XKwxEk`K`cfsXUe9G(GQ|wX8->w@NKnE=NTFWSxA>#TrqCJbX zkK^b}I&80kzIx^gGqveT3k{?iX!c-gs}vR096e1`TMdb>x@l=xD446z@^ocR<3E$* z6``C??YznaonEDua=J@h?M{{F6=-zjGcYUDm9ktv7n6jyGi9sFbF4Fu ztBteJ_T95L(o&U~EaujXQT3@55>}{fLuhsEQ;s!r zYY)YB2r$y@eo$M}C!Kf+q(O8S*wOusl{UTKlFE{*g6%A*OG~j4rlbLC&CN|B*!x`$ zLI))qX;=0(RYd|9Py@z;7Zqz61xkTO8cj8gE}BiZByiXT0ji+v*;GhPW z2aS)FDW?{5b&A$iMb$*HGWWYy1f;c#EPfR!wp-4U&btfBI=eK5oZ#ZNFnV!uWvobU zvO%51#H&6my}1|N@+7D;F+CuJ50TJ%SY12-t^PuJU`erv%v{cSGC!y9JRW}_f>Jq< zU@6)O&%6_8Y%lC^Nk)lKL5DQ2Hc`=*S-)*!b9HXX;FuYLHJL&S64*d;*2>NVvDCq| zNoqu$KNII}(juxy-9M(`PA%cIvEC*PTxcyqEcwetwuEVtDi~xLE1qGD+eqq2frR2v~y?j;B?W9c>6*j3HEY!bOIt2mN!wH_n+rRJiPg{<`Zq}E@h z!5VcNw8e*0>E`6-Zrl`?s>O@5(mKB%!VyIZ6*s(qHSP@4siP6(IkYsIB7&Q37)8k< zuBD}#EQIkhF=@6x{sha+oD(XZD;}N5F5EUrkD*1r zXleSKVHh$Rq5HYR_^O_l%@ah*hSi}I3WgU*!fkBPq%C_ktdAdd2`nZpZIzdy(m1>N z&e0kz6xV`2o=K}fuI3$2BXg#SwV|S}c_PZKrnr4d@g-}TBc_~h3ooXLY9(iJo8e>` zecoh0-W2BJb2^jPKArmwzcG<1H19>YM;<5OuJ9KHCpk_xhvZ-Kk6Gt|jP)U3wTx(? zB`fU0$^)BuvI)*o4cSd~&Z=kCP-9*@rf07wSwXbgG``NRTJUN4dbM~Gm>`9&Y)%@^ zfG=jv_kn~;Pt_#0+C<2h4Un^Su667}=1fzlnX|%D2c)Tic8(b#<`fML%1qfdH3I&A zyENMlqIsp&o&sg6rKMcb5@@uR{n#tHgID#5BP3-NjW*EMj6D+REE+0Wp730)J(N3w z3KM4F6|DlS6((8@BQo3OA9+054~TQx4MMr*8*0WVx0IcqO;LRK&~k(k>Yz_i?VIqjm(|4ZQHhOW2bG~ zwr$(CZDXZvTQ{rDefQLC9m+uYEA(4wwea&F@{32rYceY2vs!XFIk~*tlXlf#S+=HTPS!9<) zxO~>Bxs!FC@noW+eVRctb(=Ccyn>by-&lQz2V#l(#;~u#TVWZo0&VhbhZlHK8zR-| zle?l3AcFvfpkck}_*T?d!h_F_>o9cPDeG3N51~FfrBCgJ9b`(19NYtIw&f0-n&SEA z!OF@Ds;>ea1bQDU)bAMoKvf{$!CM5#slHlP5DCDG{1k!e;S`9?=8AbYnd#*bXBs05 z+utn4zrHyWXSWdXlDB2XLq}fWYu1C&hRF6u<*YnBrklNN_K6&SBk@2-BqtIfTIT); zcx?|1WMpjTG@eX!!20wjMZlVW zP1^mCqOTsVPo8^lRr-`v+eN)bWNU=Y-yp&KZfrEhh{UXuL6&rNnupb<;( z7Gq8G3qs(qWJ9!kYad~~ZF7@P8>-O%V5Eq(UdKe&z@!*Yq?|8HIXrgl1hq2Lw{}f4 zV)>L#%)E!ulH14xLN-4$)O8J?atV14^MaDm)Hq*>1Ejox=y)bcFB7?UP@SM^Da{7s z%~D(A@04T63N*P5p}{3NnJ|9SPIbzJAdy95e<9t5*$knEr8%}U=Qkg%3bY5 z`D7szc%U;}IS>0J`AcTcK;)bTn^J7gqElvV)vaVIK zt}W0!_5tGOmJUx|mqY1iY_Gt~9NenX?J-|}>iE|@q-F_VfXy6FEb#I}vS`al4J6k> z?7p)u-$TOqWp3cm_n=9l>y1rw7-l{z+8@h5nIvQ)^7(I zVe%j9x(w2;bp`u)SgyJ)EZJk@uBnJee~N!03XsPn)Z&c_fjtxI!{+gLP zXv>YzEINu6(C?2w+9^#II*ogzzbmeo<82v9+R};FL82Ij;n5ATkv8K}A@~=gu5YV^ z&={h*E?fGlOi4}A#NADn#AC+bxT@BW8c44N#;ko!XiZJTpE0YLo$YO+sxb>|6GydP z8VRqQU$`cdTt28r-s#8h^M;H^oZ7Ij9zt>MTObo$X>$fOL~ zW;@1*Xh=H z`w?x5v#a(z|SU+=?;u4)^wXuc<3xeT>?d}YJP2op2tT$XE+`vym$4x?wx3R`X zns|#!c>gxhP4o3MT~YJjT|%-mQ$yH6WZM|iSciBSaKkftfjEe^kL)0xY+PvE@Kz2 zcGs$Hnh{u{B|TOivj!f15^`UIAfVg|1V}03hy|)^j*+^_FG2UKaqM5jXQ^sPl2j=C z4C8K*qq#daY?45p&mlovFPZ{eQSHQ~kv-jw(Gj8cju>00{Q2srK<`MW<4XjG_jIrw zDW3Tt!rNkX=a;#^^Eh$lKIl0#VtKDr@erNB?a#mE>VM=CNN$V-jFLgSV)l9EOTN?x z(_Xdy-~+?Wl%@7=g6bdt?WU$qI=L#L;BIP1!%;&s;8U&(qutMEc8>d*>$Wa;WnlQj zlN8bum68kRWI->98@>O5Ze$t4CW9M!?}0xf2L-PJC7+L`*P|-4FnSo?^Hw63_DZ4Mdh>y%+xF9EAMM}F`P+}rfSyiJT}GB&cdXA)|`wDMzZrsF;sYEc;qj=xvwUYPlDf(ki{_D)L^ z%u7W{bD7%_>k=DFnVT_K(QU+E1cLO%BTI5Qz#(gcZzt!Ad|>UC+TFIfaRPB=**Y~_ zo5-pmd%UlMN2HafDqE{G6-{4ffi59HOIKW@aYv(-c%u^m?~FrLTKy$T$Oo3l;lldB zTNHJ)clnxJMy0EgyjmarPC*i|EJU6ZhRl(nkg%Te3|ZCrmZgU11-9v zZec`*wTS%k!9GqLvidARTjV)_!c9vB5c-z6Z4Z)2Qhyz?OQ_lriN14=?Z5^Er|hXo~j(<^=Cgow7Y#0Bq_4AH$l z+Xb!57HbZ}g2T!_6^WSf6_Qa+a)c6lhx7Gi`MJ5AtD<`QV+g#43X!whMIiD4;8(-PXv{i9fz(?mymYk@`~V_ zkh*Yrm6o}JIR$XN{woFi#5VjycA*kPLZ~%e)9z^OT@E6-MD9bQn#3sy{CX95xOQ0p zMc_$#s!$W5VXS0wtt3mhs%M?3Q8wZuDzBLo+2Re|xOEXwZFo_O5BZgbv zc`enB)2$r{)lM>M$yTZX*bBAHOBK;RN7DA5J(n(6Qhl;Z_)9L1QHUsyn$4N>vy6yP zG<%@CuR&2k^e1W~72XL7}6a8B3n)mLl8^1??~>T=-k^-MEZhRM|% zJ4MSbN@oeep)vC7sR}~#E-|HDYhq)dL;NQ9MN;HJhP<@R^V&(|!FB7%zn!+K8G?Ia z+$A7vO9aJ(QS4?ErohVdRuiS+wMFMPzf>crgP?s|;nMzwpCsq-blyI58Wimm1N`ph zS&DyYmc#8v66~RPQFHw&dw?u#Lx<+eD%K*Cw`t6+*3d-_K21dDZ18G!DV$Oys-iNv<%$0DxJC(T)mr=V2hxwv#*h-klihf)6MNF-#&Wx`0MaWDJl8X#I_OK*u#8QUuHoD zbC;Dr|2$Z-Odl0I@mx@-RqN6S1CX9#S(8QeWaOURbDd{#C7sa#K~l~~1X9OS2xzZ7 zvsAj`9ww-3>Lq76z+DG4i|w5z;p5pF)ca$OO!rER4$5~OCAT>{qjy3NrPD@q>%`os z7@%3>4ZDE24z-igZ}ubZd1hE(@}}&$RZ%f=53LSu)g*W_*sev$z_D=m0|$L%Wel2` z!_VJocSv_e3Z^Qs9@Z{+_y-pBVpI>OKQlO<3DuLc7YbF_6U?!{1A9AG2nkcXH8g`- zX5Tcq4{?w)S4!M|%wCUP!EWh>ggQ%gkfh*Cc6fujZuKz4Bkkfn?~{c67WeXn5q%Tz z*iY$Uk?xf3buSYKC?t8x4E3kfLM)0mf`HQ1D>FMrjBHw>%97THR$y|A4%FM$Niuik zTEQqZZ|Q-)c|-@%VPg$q|3=`%mV9)Pn@YPEjGmrk)`pZ*Cn&hvpO>9r-^WhW-)G6P z(o&LD!68{hWqzkd@oI+Hs?3FjEX@@dIp9!V?B(dny}_0>s*=O=CJ3_}$lg|1^d721 zt=OHX1uoWO#p4gv$zuUlB#_(bL z^%HL!;oVbCW0ayWl{N%|z}$RQ5yMbAgZf$Xj!}jhEVr9c9;J|eHK|-b7W^JGewnI_ zqLAR_BhpJxnBf;_?;2`Tt@K!&1po7&;{k)&%b5BK(7mtJof$ZcXT^E2DOZ!$i`)yN z<;E-edpq4Du>X1Ig%p2KpudN*KL|5rTa|4GltRMwKg5JBY9zjFDg z{UhLyN8AgPvE*WDfr|%=Ab!d#0Uo3mu+T+i1e{L!Ju;ksU+bOc?VHTqATL^=Y_XzAFqr3<>Q_j}c~ z!J2-;f^%NFQE!}XLa$SIQKP9+L%MbshXwMi)$3aDtEYr^f(OG2IuO+vQ?0m){F z`3+ygi*3dFQ$(^9$8ko$tPu(1O81MvJ_pmoaW~fW-@3z>I5ALSXbjWxPF_qUSJYIEX9z@FWkR2!mVGZ0&SA^@I z=Vw|AiC?;%q&kO+@{NT^7bPRb8>ID0tz9c=JQXP~@{-h5`MdCdFl?dj{Cj4&K!Z?U}9_tr|2ioSEa=wg#TIj17{7_w>aTl9gl_H(_&y4&yC@UR?bM zg!%^;5BF92NcUA2R40IY!Tv9H;dJHHn;`#;u(6uo_Govj0KMGdczlDAP4d3XNrOPz zj|)$>2TaN*^q;Rl-b7Nc*>w!X9D?@Mu@t44@CIsFU2mp;Kw=r!Mnqmvigvx+Y%!Xn%)*H z(G8j**0rTfCuXvbt-8j|7MjVQl?e=(B;n*Duy_@A+R{{t4#9;~hJ@U?@TLd`;cyWL3;mg$UbzbKdPA>e z@soQ%;aSGL*cP(U?)#D53!CkDN4$Qitu5_Df*|Idw1Ec!gtYiWC zkD8VLT4w|e?ez_<48GHP{=3pB{gczibym<6-@@2Hlpv4G>!T^2(IAVbj+9$eNiO7S ziWCZhjcKd|*$dW{bE|^O7whsJ6h}HxVfhQpQwM>Jp`L-m*sA>uho{>Uq{g?BQc;m! zuuRRQ#6YgU4Ht%Kfhh&j?0W1*0xIa?(fq8_FvypI_9E9jIBR&L2Y?Z7Iqcd0M)gU( zzrNqR*CJgru9IYlNud>VzgaVlhAcYGv9pf-BwexT!x(;%YYEksbQI#4Cm9ZecBlbp zZe4c<%;)#$(?)3~ySx7$e_~6`7AKTT^T|^OkGJKC@EZ0a{)TeZb$4oH2TeWu9spyJ zs|v)2od_0MCsEM9fRp$-o@cZv!@31r3u5Dm$LKgDX9L?}d#0`qnoh*AV$d-fU$3V% z3aYeKt=JD`nXTyj=$9xh$3-Yd)51Kczyr<9Z_8j1cyvK^W9G3LsMYSY`K!f?K7wz| zDnvx>~(;k6B!H)U9V3aX5_w{T2gm``&0T#ox zBO0Hp2A!*gG8?6mfDB5=Ar3l_uUtT#Y`}hT4TW@ZO{IW)mOjumkKD2kz)c-%0h$Ux zt`YSx<*$dlY4=0&gU_L=15MZdfIPrFiB<>!@y0v-!aW7sWqPGhC33?u2fGa?+=$?< z%G9?)f2H-K9!Q5b!Q1GOs-uxwNg8g_FbMBQy34r)NVUxqNr~%(57mTYC`33%=$Deh z5r-GJx5$8ciz~#JfMW|bM2rty%}6S~-X4f4;CdyZyQZ_=vQJh839G*V|IcUR*Zjzb z+_z?7zcu?GjH3LjX8&8MiIe{z3J!mr5jO3iqOZY=lfmNha(_#;f!|E2M4mS>Pg7lJ zx2M+@64g)PoQd2T7#=b7H%yNJo{=Sj=!vgX!NkjZ=sA z$#V;eoqz*Ui@u4FUj>xcHMvONgY^~5SirB*^V!9`IQ7RJUi0tGd?Hg5g|DG{llE)+Fsw}oR62#0k z{BQ@C@RnHt{g=4=z8BXkwX}=%gw6q3G11 zdZDtv+C&KSB&)rH?EKD$6$V7S;U2t0+{8O1y@AIGt96KocqHa0A?)^mTIY{9P_@K~ z3r%vh2ARJ-Tm@aM54ym1-h1qeRGU5}kdRP@_ zfn{e*@~_vrJyGXgW^p1l_!Lcd3rzHrI^$K-`t20M<;19DZO{Rx@zvxPp_c+m8hU_5XdtelZb7KShexm4mN(7NINBDMK*R) zo>pz{m88p}T1$zk5*_qM&;CZ$YV0N#cVwmV?$1lt+3qWe&Po4#L3zQzz!KAhB&5A@ zydu0Q7&5uL9MQ8}U4==sklN#p?|l&Gw3xNl;*oIKj?4(yhSf^t?STh<;&$E5cBHXH z0>oA7g}Yq^*)10n=R^zsppjJjny6m(cM#EhX>MK-A^?`@Xw|rg(oMNHw_R|GVdA4* zSdIxgvW(Y^nd8ps_e3C6P!dIgmIAdJz}`QXk$6RFSjeMbkCJ=A7MnoMqjC>}bLIwi z>$JrO4r{B{EvoS9MgWlqya1y@2{GCMb!ABxpJoPuKjpCeaKRCR+E}>NIg3yviKHmq ztxNUseO+;6p(0>fvs#Un6um0L`L?xKcvd9El_lU>?tEVn(BWXXIUIf^K0Vz)!i0Xy z8rkX2cLrYg8lorfBV@WJNYrg0mTtzWJ&t$OnpB^TR>@{}5G6HQn8;>0qa-^@zUU1S zEMNRRn*ZCa=+Vu7nu_y4xUg>*{dpKHWS8My?}veeIzn6l%&tE#4!&p0rB@6`>^`3r zYh!lxCCUPJK98B|ZHu6Mk`tf%*-AAmjhM-hp*oYFpNUX-2(D0@HeDZS;IE-m={O(1 zj&BHRt4Q&Fq;!x+U{S4#NtazflK#EZ8lD5cOpZW9Ip_ycwi!?oIpgr@WnAxuEbj|> zOgzmMd4twDYLXJCwS$9(jfbVS>l4 z;_dIBmA>A1*^m`+KVMAMtuuwWk%hfdMzTL(Mz**kJt!02N51&EgHmg72+m~Ii^%e`~XeoDzar6+@%rEPPRGg9M8GY-MQ zAOq`5y_jOzemF@*DEBP`U`>5`*v<=G zyjJhA#~`q0$Nj0^|3(Ah4d5icMf>r?T;TtWNBDoYrT*W}9Sw+Y=T7QZcI!7dDpe95 z42T}g8ZOS&C&Vux1Q#fj_@@| z7c)o}{Z_Q$Nf#?(8;02Q`PNi}Fb0nj*mx|-4>1oy#7hFNpG^`v5cf0!RG>izv zLD=0*kLy<;gg#egZBoTHNVbe=_w*})*#XDmUmH65PhRIx4DTRF$)E820(Nq z(Z@*FAAP_A;`gJZ&96HplwCj(ZAY5VSJl6IWvVr>caS1^NLV0IAB~ZtC)rrfbThLA zc@cC0MVQ4Ts8_SRbXtKLJdYZj!fH#Kn#rqLVJejz>BtmpEg@JHa7j|eHt#7rFeq-k zKPj62^D3$tg@-`fu9sf%W5$H~_Nhp*!BI<~L4#bpG;%`od+sVXn4~iLHp7qma+CTJ zmbKk(=&_@&$^T-|iM4_IgMT@F=MKMV%VR?cPr*1pjhg&qG-n!WHh!|mDRs4f* zymbG@oaaZSxqjstIzs8trnH}!#lGKMTEt*mi@sV#adZ4kW(JM?olv1jPzL0PpQ4@@ zJ=%umR_`NPVxqvpZ{z|3B#mjO1c$=mT=MB<>l6NRP~X{?7ml?ybM`iNd3)ersjM`j z4o-0ZMdrf4t?a~L5hE_z$zeonmTpQzxJ8&!ncj- zRzs<(9|YIe&S#iU=5Ag7=5=x?AHMiCQ~T9u|bbCAvbHD5@{r(Uvle zAw(Bq;vLWF3hl0UVs|yQLB!QQghtZ6`A)ahW24E#f=kN>Y|!+69yXCaXC3B)kwP!W zFWZS&!z})+m%uuvAAbIyHKh@TuG&X4aCLep|JAkjvXB zac&b6UEA*B!%BLtJ7n(1)6!4OJ*4a%vj*jecq;2eIkB4TXY}47(fXBZt!c+B>ABhH-n#5WNaRQv8*uM8i=L0GZ zx*7q?e9e7>p3ZyNJeLPXI8M*C!CAWyfOn3|r@fz6ZZWstM()qV^tYa>^gq$&7juFl zD1ov3se`TA{A`s%`hH6!=H``2aY{g61{a^n&K~@5 zrt3P^Rd>=`U12@FJi>7}BWT`ku-fpmkMH-8mG_0buHjjx8J3e@PTK(l9J|1B1X|797iO3ooeuJH>B8idsH2hlhwdQ(7o~v4@3q)rJk3t!iEbhxJgiE!5!g{&YB?O{HBhRCZ%c>2`mdM8k!U@D>J;#qTVdS8c)Ms zPxs60#j_6PZIKlVTpHrb7Lf!-R|Ll9=M8(wdA#Tnn3}o7C2o;Ym6&Y+;38rh*fo^- zM#x8)ZZTjQ2xsm2qbu)KO+`|(R>3vwFzhIqq&nK_6zuQ8^ZdIj@kzUw81e2iDS@?1 z((r*xowd^BW3iHGq7>je6_q0NOH4>ElAo#nk$=7YgOXYc10InnWsHMU@GP(968L@PBOJHw?GUMr7U{-mF{ zODIn(zcw9qC#{{`+BzQCaj_FtQCE>8tJo6V*eZCd9E+I*=QIbr>M{al`?S-^SAyJ= z=rk!}{7%87RWDoAQVSqcM}O*xxbFo;U3A;pE|#CUgO&A|y^$qB#adv=21 zMw=mU--ZzG`yf@Vl@TAj%@(5P&w$aI5Xe;xk(UcE&4Y#*5Hf*9(JwU_u&V_s7eZJQ zVps~7D6Fy%1tyQ=QL5P--y;4mPku}*Z%BL+Y>?TN&yzqEE{+wk#%r2Oo#EEL%>IK9H zwvt4)^57gAAs+@J9}2Cee7Cq?%=$RRQ-(qBoci9)`aH07H^ov&K<}*jIE7N4;14nC zX}27~X}2EWbfC4-)l$y17&U&HYN6nFpqJb*2Ep{>KnlIus%Nuu;9G;1=z8jfanB=E zJ^tyC$uaP`zE?h5lXS*0vWr%`fOv+^kErJQd8K{)mcnc7H|N8$9naNeb_E@MjN!o^ zH#j_Fl*WuB^9M)ahR%U_q`!DU9{}TB+P=`C%fbcv=iUnv=1+uIL=6Xv>W=Zb5U0+H zw5?F7J4;wWH0ID8H{vzXsJw=m9Z#$)^q%G=i-9-cO0@iI1mq3x-)mgq2R@isg+Lb| z=L9vZ%?1uA9rmB@2#_~o&jXXB@a?qX06%bB3gS>2ov>p8Sr0hlP!Ku=&Vbcji4wTK z#Ln(O;e+|A!eVt|=l$TdIOPL-E&Q0g>8v3sF7Q~4%y?Zv1uozk1M@4yS-nm-j_0t` zddbg!tPr*aHg?$n1b=L*StZABj%r0Ze1Zp?Zwzm=&hu+Ob`3d6OlqmT0{l6UP#Q&4p&v|?#C*jZss65C!fJTuI0e1j2+r!w_atf~SGpp!anv1$xKgK+ z1v|ral-R!ERXi~dYd1^+rs}{Red}-UPf&=H&ZJKuciv9&DSp=<#{!a73k6BKe zTUJLOmU(m*2QP8`$_{oxJP}B-KPl@GUa={mM;){qFzgK*qCrPVv#3u%6iH=be)GG_ z9S&F#^6#MStQ59vpvXq z3MFzD5Bk`XeWm9bhY$G14*H2zd?Awt_ah1m;uGIo4R2getp2nXD50^FEpsI5rk6pk zn}lPI+`YKt?yf( zfF|^e(g)*pk+b!acJNXK3)Lnt>s{<7NBK`QU#4t`Hf2P>w!iob?GJcvl+)d$0(b#f7JtuC+yxr(+UG%3`}6Fowstu z9A33alhYpN$QLnxQ$Z=(_Ib>*`s9g9_;+wd@T+x0IKgyv&^EK5Wwc&Ey$9Nq+W1qJ!7Z;g=sOy$3nnP1=|5H&7U(<9 zk6uHdUL+vC1YA`C*aie3TfZEC++)mNKr8ERRrK@I}BiJcv;vK8sI*sC~U&K{h(qwTy%) z+l+n&zXLUCB$t^;?JS3AmysjvEC)@N8&me%pcOP(aCg_FcU%a18~yoj5mb-cz#6O4 zXZP2L@Ho;9umWe{Ni(aCR(%cPcGoSh)0BFc)`{FDmPT68(NqZb%=VR;=6q}YCR`D% zAcU{r66TVRJW&I$3=?y?T=5?E`A@|J%)C$wgjQqHTyZ-iaX&Em+(`J`Q25%?aXY_P zFzyE@pBoLI8xEfv58vw#zP4oC3j$FbLQx!c=mO^q#_x}~sLWozhP(CD3_XV$Bs%|2 zr(G##PNCg1U0_Ib$hKPstSi46^Co(p0S@&;?jrobS9IxndeK^0$!^%#Xt`HQln!br zTXZr`QpjIIIGhQJyi!5RX_8$9!rghC*pTf#CyfD?3kR0udS}m)-q9O`3``zg41{v@ z8Ddt!JbNQ|F3dIks}0g8oT(Wj!uHlPV7qSX{JsbLqBd>*$TOrw0Hpf11{z?1l4BA{ z{+In601y$M!1yhOe=36ESA}J&q?Hv(t5nUy5;((@)8%-1>((3~ehHAz_=F>Kx9gGC z&6n0Kmewtl)-9FxMJ;isC9vSJOg0Y~w)Q@UZ%%P@iIqj=jPMgEF*zANHf9Z19o7(< z*nDt4-skXAIiyp;>k7bnOh5;6Md?PVy^)-04i63{T&5dvJZH#dy31bgGMq*C^xS&lb5!p&F3rpvt+L!HXdHIkapqZUPFE<2Ru( zCt5CU9a483fYf`QPW=ELY8oj>& z8Z+BOWGb!!0V6%^#hE3SL-m>&#$9#XO#Vt@F&UPNbzqv z!~cRVZmFdh#NQ)>=vB9Z{m0kO7JJIlNP>~!e8uz_-O<=#Ox37{!5!DEhVmsP#w^@k z@hlO>kf9kBS}~vTMHQQ%oB+*9mH5(eP}Ij>^z3NV#YJqlaUzXVhps>BGf;k4RjIMr;y zhREL)ut{O*GI0zkbyy+n`~Ae`aYZ2qX6`-+kj;HzA(y?wuvbdlCGC8bd+dpBg!aU9 zd{Kv)tdi3q0F2+N}?+5AM|6wZWzwb12*gr$< zqoUy>w}x+b0AU-{gvwV5ty_sb-}q-@VQ&2DW*-Wt8DWkxaFvq7@NSV>$#3r7R;XL) zTbi8;5hk&0W&vwXpk5MYSRejlza!$*&z1sQKv<36i28-byKqO=)KBYp)b6C){? zl&Y+09^FMc1gm_oY93$&F9@0y1a6aRhauapN*E{|#A-RV8}yxFKfJNV2T%WQxD!o{ zsdeuQVY>+|(mC!ZcuNxDnGe*9@6;o5eDtY$YVdA)cO*dtgMuo?MQI@<6-8;sdjSr7 zmL8##fLF`BS845=@w?FPfB7dB;b$e`XR+wJO#E|m%yah3$iP<>eD@Od@h0x53^L47 zNBP|4nCQo*zI->!nHEz0=k$98fvf5khu{(o`BHEQj6b%m#~~!h{Z4KTYe=cbv($== zg!_z)pjD%nkK{EleJ{46i91>6gD(C`*!bhQW>YsR{`&Gl818t~(fRn!+Yp&5)cbPi z&(A|xrDwX*EEX@#EfE8b`mUyV)zadN|W-%G!+nNGmmYP_LbQ@PDd~j{Y?I!>*)uJkzv^XzJjVL#dse z>NYh=hUd0tD=)LgiOef~3g`0MT0L~gv9QzAn+g#yE<#_g@hbN*LAEg*Ohx1Q%gbme z{~%oH=|`D~Y-3wF=9g#f@K>ql zT(5dYQI3lOvxQUv)l4>^?=KFweQh<8Rroo3NWxJDZeo#ZcbDOm>Qxf>K}9?-GA;{L z%Y#b=68K~V^inwzcwI+)-APy#hJi&$Tc&TxuaHUfg9)%0?d!B)3l~a?l!KjB{+6C7bQN2%dB=EPK;@z3_Jy_Q#&PIYBy>xJY zW8k>n-Nqz3t#>Tl(*hqEvvhb4F$JZh(TG4K?jAAw*Yo`jsNUDA#;BZ7-BJmJc3oR3 z|4xMnR?}ypboW}cQ_YmK?<_l1E*Zd3Yyr7}{!CN)uWLli2^ITb z+drS_3F6yYdG0V*8f&5SZ^mFKF-{PMd7L5Y;_AOa#}i8I#TgC688Z@0Z2tL!l&CAg zy@Aa8#khi~a=BN(f}|1wRo_A1uhy6GvX_w{&^|&t!`z||a}Rxjg}MCiXJoAcBo2>u zjzhPP!xzW%bFDCzuJXD_%3unf?ffF#1}cXmD`!P=AJge}9sAyP9ptOrH(-$`>_!>h z1xJWFG&%iv>)`LlwfAeyhwkqOy8|jfv{3Iy3$0+&-wcI;h%aaQy^~i633l*2Jua_09KtdBb>*&1^JtBh7d*X@w>J!v{s%8>@~ycQG9|V=?p@eC z|K{4~l?*46zWp1U!_fMNm*ID3JLub(_#c}u{u!)C_}}IWWlIGNrSF)YNvos)C@`iu zpc+IWP%yj%^)g-xKgGNlbw9<_oBldAJj)f@bSVEl9vk70B8JoB;IijAwBDbm3&~MY zb7f*$pdQRSMr0 zMt{Khn}XfWc}V;{G{F?4z!VW@RAo>_y+!Mg{^;ot#h1%@;o5I>yjJ`LN?`x_3d zmq(j1{LwH-G7oO}Q>bhuL7|p*HB!^jaE$Td)GR-R`pUo22SUVV<8Mf1o;@O63s zeg!FuZu?0UWEVx#iA4Od9n)~0Ij-1>76d{QOEz_YgBM^ibzR#d>>pjgR=tI0h1z<+ zqJOz&oune{-Qx4<4oCjP&caO>pW64szK=WSFcM?k3AtcY_#td^9e+`2V23h?Z2&O@sY{mHUKae;GbL%+5*n4%795 zAb4kpxS6Kw?d~D`f@gTAn7FB?o9^x{e`ij5ZYI69thm8ad~sgcZe6qagJC(`8R7&$!?5h^ zIJtaZt2{%`hBCy!%vNp#vO`Ewi~c^t7MpsKa!d77TTdbMDYR_Y8TuBOIEXeJ{|IXU z9(7))sQznldv(SN&)ypwlfE4zAhgMe2Rkp~(0fKWSZflAH_4Op(#Rou1*UA}?(64Q z|1J{nBIDSbN8%@R=J(Rm=a`J%EA%99-7b?8&p%dAMq(1;*94G&yZYtlaT|rA&ItdG zGV-p{aJw}#4$`zSCklf_Pzi1onuvF5%)%w}w#4TnK7sW{)aYQ82##Q$Ip+=qbJyv zT6@179tYXLBc(6LPl(dil`T*gje?$<;#fd!s4O7hq&6*}H}UKhs#E9W(Mi|wj-8%&4EVVjapeTttbrbg{DX z4?)cwJQ?dHZ!Be6)5)axh?z|Nlr?OpD_?cAUA(9U41i7~iwH(A&MeucMAN zTCDPH&Yt(Eee+O-2X~Lec!T5$n@Y|}8}46ptsMUQR`}lvQe32ZH#jK$Gxz-XlFs zPO{7eSVXZSET5iR-WC|AyhKN8kPcI+c4`pH1Ak8eERZ@Jf3Gae{8@7F=$XGy7D%Oy z7E68kJNN5U0_;QP7Jr)Ll9I`}^8{F#YS)cY=dwKzU+yq+X~Kt?uS8~p2#rHY0FNUN5ofhPIeHp)V;Z?#Ebz=KBo7ehuaUM?Ybx7pF(-7{plyzQa&tH!lbU z5>t%pd{MSjjvz7yR1~t8Q^HC;{Fg2j>ie-EZMEB5UC9|2@ZxpaE-Mti2VKI_AGcmV z5q00dI-gtpJJ9jv$Zkh|-kPOkuiN3h{ED>Rkz{-^Abk0oCFVs~z;w&AZ=b6|P3VnG z))6UI7ZG|p6ga)`S9C+f0`$ZX+CdzXI#Nbgp~APFb;iri81YwKx1J8~Dt zY!5sCxCg7xJ6ZgRBX^y^J)HA1r|v7%-UT*EE;ouZm@JOCqH3M|RL$hyO_VeFnQUI#J9GGxFe#j92|1i+}OR{jMI+%;*qT}aPItzok z5#@LtWi;VcI%9sUo&{u5;EfI?WewqaoFOH~gv-4FRFcuYn#FlS0fgl|Z`vHO6r>tx zWMzXR!tZZRj7hyJ7@pL8fv!2f!dwd0ub+PU`JYcz&cbyW_}-S>6)abq?w74c@5Y^1 zdpA!wUWh$JeF_0Ytfzyu_$I8{A+XXT!~TOVpfD(VgxIms-jut5{J4F(mq(e~o9RGI zFRdw00qAbB;H|iR1v^$soGm9$2Ll7aF3)1n=)HDrPPqVZSHM#kY>(W_Gs??zm&bcK z3J+I{=~2x8GzyT#scp1H&3iJPonj->rFPWs#-wy zPWS%v%P-A?!RgNDD}2f!nhw^BK?v;*(rvTR0w52n(12L=4IHWB$OfOyq;onT;^Ig} z&b2MWg1H+5J%#bbU%To*zfKMCGee2#wPrsnc9yU|rD~ivwy^+9#)9rZ$kpv^Z6s?k zlLK!UdZU7r>EdXilKo~6ZPZyShW8dD=xX|1oTh=ix*{IN_-Ug>F6VWjm)576^?F39 z$EhYAe|zdnD+%U0gmWCU*wal=LyGmG(&0}`syRzU$5uAi)0ycpN1 <`HEE7%0HR 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%0DS81I56*~n%lEnTo*hvciZ>V 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�UQ62KY!|i&8w45V)>p9 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 literal 0 HcmV?d00001 diff --git a/etc/logo/logo_trans_alt.png b/etc/logo/logo_trans_alt.png new file mode 100644 index 0000000000000000000000000000000000000000..4cf6e32a72483038b8cffe0f12186a1ff1a1f73f GIT binary patch literal 14298 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 literal 0 HcmV?d00001 diff --git a/ivy.xml b/ivy.xml new file mode 100644 index 000000000..ede344fc7 --- /dev/null +++ b/ivy.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/ivysettings.xml b/ivysettings.xml new file mode 100644 index 000000000..a902a3a96 --- /dev/null +++ b/ivysettings.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/net/burtleburtle/bob/rand/IsaacRandom.java b/src/net/burtleburtle/bob/rand/IsaacRandom.java new file mode 100644 index 000000000..80667e63b --- /dev/null +++ b/src/net/burtleburtle/bob/rand/IsaacRandom.java @@ -0,0 +1,298 @@ +package net.burtleburtle.bob.rand; + +/** + *

An implementation of the + * ISAAC + * psuedorandom number generator.

+ *
+ * ------------------------------------------------------------------------------
+ * Rand.java: By Bob Jenkins.  My random number generator, ISAAC.
+ *   rand.init() -- initialize
+ *   rand.val()  -- get a random value
+ * MODIFIED:
+ *   960327: Creation (addition of randinit, really)
+ *   970719: use context, not global variables, for internal state
+ *   980224: Translate to Java
+ * ------------------------------------------------------------------------------
+ * 
+ *

This class has been changed to be more conformant to Java and javadoc + * conventions.

+ * @author Bob Jenkins + */ +public final class IsaacRandom { + + /** + * The golden ratio. + */ + private static final int GOLDEN_RATIO = 0x9e3779b9; + + /** + * The log of the size of the result and memory arrays. + */ + private static final int SIZEL = 8; + + /** + * The size of the result and memory arrays. + */ + private static final int SIZE = 1 << SIZEL; + + /** + * A mask for pseudorandom lookup. + */ + private static int MASK = (SIZE - 1) << 2; + + /** + * The count through the results in the results array. + */ + private int count; + + /** + * The results given to the user. + */ + private int[] rsl; + + /** + * The internal state. + */ + private int[] mem; + + /** + * The accumulator. + */ + private int a; + + /** + * The last result. + */ + private int b; + + /** + * The counter. + */ + private int c; + + /** + * Creates the random number generator without an initial seed. + */ + public IsaacRandom() { + mem = new int[SIZE]; + rsl = new int[SIZE]; + init(false); + } + + /** + * Creates the random number generator with the specified seed. + * @param seed The seed. + */ + public IsaacRandom(int[] seed) { + mem = new int[SIZE]; + rsl = new int[SIZE]; + for (int i = 0; i < seed.length; ++i) { + rsl[i] = seed[i]; + } + init(true); + } + + /** + * Generates 256 results. + */ + private void isaac() { + int i, j, x, y; + + b += ++c; + for (i = 0, j = SIZE / 2; i < SIZE / 2;) { + x = mem[i]; + a ^= a << 13; + a += mem[j++]; + mem[i] = y = mem[(x & MASK) >> 2] + a + b; + rsl[i++] = b = mem[((y >> SIZEL) & MASK) >> 2] + x; + + x = mem[i]; + a ^= a >>> 6; + a += mem[j++]; + mem[i] = y = mem[(x & MASK) >> 2] + a + b; + rsl[i++] = b = mem[((y >> SIZEL) & MASK) >> 2] + x; + + x = mem[i]; + a ^= a << 2; + a += mem[j++]; + mem[i] = y = mem[(x & MASK) >> 2] + a + b; + rsl[i++] = b = mem[((y >> SIZEL) & MASK) >> 2] + x; + + x = mem[i]; + a ^= a >>> 16; + a += mem[j++]; + mem[i] = y = mem[(x & MASK) >> 2] + a + b; + rsl[i++] = b = mem[((y >> SIZEL) & MASK) >> 2] + x; + } + + for (j = 0; j < SIZE / 2;) { + x = mem[i]; + a ^= a << 13; + a += mem[j++]; + mem[i] = y = mem[(x & MASK) >> 2] + a + b; + rsl[i++] = b = mem[((y >> SIZEL) & MASK) >> 2] + x; + + x = mem[i]; + a ^= a >>> 6; + a += mem[j++]; + mem[i] = y = mem[(x & MASK) >> 2] + a + b; + rsl[i++] = b = mem[((y >> SIZEL) & MASK) >> 2] + x; + + x = mem[i]; + a ^= a << 2; + a += mem[j++]; + mem[i] = y = mem[(x & MASK) >> 2] + a + b; + rsl[i++] = b = mem[((y >> SIZEL) & MASK) >> 2] + x; + + x = mem[i]; + a ^= a >>> 16; + a += mem[j++]; + mem[i] = y = mem[(x & MASK) >> 2] + a + b; + rsl[i++] = b = mem[((y >> SIZEL) & MASK) >> 2] + x; + } + } + + /** + * Initialises this random number generator. + * @param flag Set to {@code true} if a seed was passed to the constructor. + */ + private void init(boolean flag) { + int i; + int a, b, c, d, e, f, g, h; + a = b = c = d = e = f = g = h = GOLDEN_RATIO; + + for (i = 0; i < 4; ++i) { + a ^= b << 11; + d += a; + b += c; + b ^= c >>> 2; + e += b; + c += d; + c ^= d << 8; + f += c; + d += e; + d ^= e >>> 16; + g += d; + e += f; + e ^= f << 10; + h += e; + f += g; + f ^= g >>> 4; + a += f; + g += h; + g ^= h << 8; + b += g; + h += a; + h ^= a >>> 9; + c += h; + a += b; + } + + for (i = 0; i < SIZE; i += 8) { /* fill in mem[] with messy stuff */ + if (flag) { + a += rsl[i]; + b += rsl[i + 1]; + c += rsl[i + 2]; + d += rsl[i + 3]; + e += rsl[i + 4]; + f += rsl[i + 5]; + g += rsl[i + 6]; + h += rsl[i + 7]; + } + a ^= b << 11; + d += a; + b += c; + b ^= c >>> 2; + e += b; + c += d; + c ^= d << 8; + f += c; + d += e; + d ^= e >>> 16; + g += d; + e += f; + e ^= f << 10; + h += e; + f += g; + f ^= g >>> 4; + a += f; + g += h; + g ^= h << 8; + b += g; + h += a; + h ^= a >>> 9; + c += h; + a += b; + mem[i] = a; + mem[i + 1] = b; + mem[i + 2] = c; + mem[i + 3] = d; + mem[i + 4] = e; + mem[i + 5] = f; + mem[i + 6] = g; + mem[i + 7] = h; + } + + if (flag) { /* second pass makes all of seed affect all of mem */ + for (i = 0; i < SIZE; i += 8) { + a += mem[i]; + b += mem[i + 1]; + c += mem[i + 2]; + d += mem[i + 3]; + e += mem[i + 4]; + f += mem[i + 5]; + g += mem[i + 6]; + h += mem[i + 7]; + a ^= b << 11; + d += a; + b += c; + b ^= c >>> 2; + e += b; + c += d; + c ^= d << 8; + f += c; + d += e; + d ^= e >>> 16; + g += d; + e += f; + e ^= f << 10; + h += e; + f += g; + f ^= g >>> 4; + a += f; + g += h; + g ^= h << 8; + b += g; + h += a; + h ^= a >>> 9; + c += h; + a += b; + mem[i] = a; + mem[i + 1] = b; + mem[i + 2] = c; + mem[i + 3] = d; + mem[i + 4] = e; + mem[i + 5] = f; + mem[i + 6] = g; + mem[i + 7] = h; + } + } + + isaac(); + count = SIZE; + } + + /** + * Gets the next random value. + * @return The next random value. + */ + public int nextInt() { + if (0 == count--) { + isaac(); + count = SIZE - 1; + } + return rsl[count]; + } + +} diff --git a/src/net/burtleburtle/bob/rand/package-info.java b/src/net/burtleburtle/bob/rand/package-info.java new file mode 100644 index 000000000..78035da47 --- /dev/null +++ b/src/net/burtleburtle/bob/rand/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains a Java implementation of Bob Jenkins' ISAAC algorithm. + */ +package net.burtleburtle.bob.rand; diff --git a/src/org/apollo/Server.java b/src/org/apollo/Server.java new file mode 100644 index 000000000..500fa10a9 --- /dev/null +++ b/src/org/apollo/Server.java @@ -0,0 +1,177 @@ +package org.apollo; + +import java.io.File; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.apollo.fs.IndexedFileSystem; +import org.apollo.game.model.World; +import org.apollo.net.ApolloHandler; +import org.apollo.net.HttpPipelineFactory; +import org.apollo.net.JagGrabPipelineFactory; +import org.apollo.net.ServicePipelineFactory; +import org.apollo.net.NetworkConstants; +import org.apollo.net.release.Release; +import org.apollo.net.release.r317.Release317; +import org.apollo.util.plugin.PluginContext; +import org.apollo.util.plugin.PluginManager; +import org.jboss.netty.bootstrap.ServerBootstrap; +import org.jboss.netty.channel.ChannelFactory; +import org.jboss.netty.channel.ChannelPipelineFactory; +import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory; +import org.jboss.netty.util.HashedWheelTimer; +import org.jboss.netty.util.Timer; + +/** + * The core class of the Apollo server. + * @author Graham + */ +public final class Server { + + /** + * The logger for this class. + */ + private static final Logger logger = Logger.getLogger(Server.class.getName()); + + /** + * The entry point of the Apollo server application. + * @param args The command-line arguments passed to the application. + */ + public static void main(String[] args) { + Server server = null; + try { + server = new Server(); + server.init(args.length == 1 ? args[0] : Release317.class.getName()); + + SocketAddress service = new InetSocketAddress(NetworkConstants.SERVICE_PORT); + SocketAddress http = new InetSocketAddress(NetworkConstants.HTTP_PORT); + SocketAddress jaggrab = new InetSocketAddress(NetworkConstants.JAGGRAB_PORT); + + server.start(); + server.bind(service, http, jaggrab); + } catch (Throwable t) { + logger.log(Level.SEVERE, "Error whilst starting server.", t); + } + } + + /** + * The {@link ServerBootstrap} for the service listener. + */ + private final ServerBootstrap serviceBootstrap = new ServerBootstrap(); + + /** + * The {@link ServerBootstrap} for the HTTP listener. + */ + private final ServerBootstrap httpBootstrap = new ServerBootstrap(); + + /** + * The {@link ServerBootstrap} for the JAGGRAB listener. + */ + private final ServerBootstrap jagGrabBootstrap = new ServerBootstrap(); + + /** + * The {@link ExecutorService} used for network events. The named thread + * factory is unused as Netty names threads itself. + */ + private final ExecutorService networkExecutor = Executors.newCachedThreadPool(); + + /** + * The service manager. + */ + private final ServiceManager serviceManager; + + /** + * The timer used for idle checking. + */ + private final Timer timer = new HashedWheelTimer(); + + /** + * The server's context. + */ + private ServerContext context; + + /** + * Creates the Apollo server. + * @throws Exception if an error occurs whilst creating services. + */ + public Server() throws Exception { + logger.info("Starting Apollo..."); + serviceManager = new ServiceManager(); + } + + /** + * Initialises the server. + * @param releaseClassName The class name of the current active + * {@link Release}. + * @throws ClassNotFoundException if the release class could not be found. + * @throws IllegalAccessException if the release class could not be accessed. + * @throws InstantiationException if the release class could not be instantiated. + */ + public void init(String releaseClassName) throws ClassNotFoundException, InstantiationException, IllegalAccessException { + Class clazz = Class.forName(releaseClassName); + Release release = (Release) clazz.newInstance(); + + logger.info("Initialized release #" + release.getReleaseNumber() + "."); + + ChannelFactory factory = new NioServerSocketChannelFactory(networkExecutor, networkExecutor); + serviceBootstrap.setFactory(factory); + httpBootstrap.setFactory(factory); + jagGrabBootstrap.setFactory(factory); + + context = new ServerContext(release, serviceManager); + ApolloHandler handler = new ApolloHandler(context); + + ChannelPipelineFactory servicePipelineFactory = new ServicePipelineFactory(handler, timer); + serviceBootstrap.setPipelineFactory(servicePipelineFactory); + + ChannelPipelineFactory httpPipelineFactory = new HttpPipelineFactory(handler, timer); + httpBootstrap.setPipelineFactory(httpPipelineFactory); + + ChannelPipelineFactory jagGrabPipelineFactory = new JagGrabPipelineFactory(handler, timer); + jagGrabBootstrap.setPipelineFactory(jagGrabPipelineFactory); + } + + /** + * Binds the server to the specified address. + * @param serviceAddress The service address to bind to. + * @param httpAddress The HTTP address to bind to. + * @param jagGrabAddress The JAGGRAB address to bind to. + */ + public void bind(SocketAddress serviceAddress, SocketAddress httpAddress, SocketAddress jagGrabAddress) { + logger.info("Binding service listener to address: " + serviceAddress + "..."); + serviceBootstrap.bind(serviceAddress); + + logger.info("Binding HTTP listener to address: " + httpAddress + "..."); + try { + httpBootstrap.bind(httpAddress); + } catch (Throwable t) { + logger.log(Level.WARNING, "Binding to HTTP failed: client will use JAGGRAB as a fallback (not reccomended)!", t); + } + + logger.info("Binding JAGGRAB listener to address: " + jagGrabAddress + "..."); + jagGrabBootstrap.bind(jagGrabAddress); + + logger.info("Ready for connections."); + } + + /** + * Starts the server. + * @throws Exception if an error occurs. + */ + public void start() throws Exception { + PluginManager mgr = new PluginManager(new PluginContext(context)); + mgr.start(); // TODO move this? + + serviceManager.startAll(); + + // TODO move this? + int releaseNo = context.getRelease().getReleaseNumber(); + IndexedFileSystem fs = new IndexedFileSystem(new File("data/fs/" + releaseNo), true); + World.getWorld().init(releaseNo, fs, mgr); + } + +} diff --git a/src/org/apollo/ServerContext.java b/src/org/apollo/ServerContext.java new file mode 100644 index 000000000..e95e1b4c2 --- /dev/null +++ b/src/org/apollo/ServerContext.java @@ -0,0 +1,79 @@ +package org.apollo; + +import org.apollo.net.release.Release; +import org.jboss.netty.channel.group.ChannelGroup; +import org.jboss.netty.channel.group.DefaultChannelGroup; + +/** + * A {@link ServerContext} is created along with the {@link Server} object. The + * primary difference is that a reference to the current context should be + * passed around within the server. The {@link Server} should not be as it + * allows access to some methods such as + * {@link Server#bind(java.net.SocketAddress, java.net.SocketAddress, java.net.SocketAddress)} + * which user scripts/code should not be able to access. + * @author Graham + */ +public final class ServerContext { + + /** + * The current release. + */ + private final Release release; + + /** + * The service manager. + */ + private final ServiceManager serviceManager; + + /** + * The channel group. + */ + private final ChannelGroup group = new DefaultChannelGroup(); + + /** + * Creates a new server context. + * @param release The current release. + * @param serviceManager The service manager. + */ + ServerContext(Release release, ServiceManager serviceManager) { + this.release = release; + this.serviceManager = serviceManager; + this.serviceManager.setContext(this); + } + + /** + * Gets the channel group. + * @return The channel group. + */ + public ChannelGroup getChannelGroup() { + return group; + } + + /** + * Gets the current release. + * @return The current release. + */ + public Release getRelease() { + return release; + } + + /** + * Gets the service manager. + * @return The service manager. + */ + public ServiceManager getServiceManager() { + return serviceManager; + } + + /** + * Gets a service. This method is shorthand for + * {@code getServiceManager().getService(...)}. + * @param The type of service. + * @param clazz The service class. + * @return The service, or {@code null} if it could not be found. + */ + public S getService(Class clazz) { + return serviceManager.getService(clazz); + } + +} diff --git a/src/org/apollo/Service.java b/src/org/apollo/Service.java new file mode 100644 index 000000000..2a4cfc343 --- /dev/null +++ b/src/org/apollo/Service.java @@ -0,0 +1,35 @@ +package org.apollo; + +/** + * Represents a service that the server provides. + * @author Graham + */ +public abstract class Service { + + /** + * The server context. + */ + private ServerContext ctx; + + /** + * Gets the server context. + * @return The context. + */ + public final ServerContext getContext() { + return ctx; + } + + /** + * Sets the server context. + * @param ctx The context. + */ + public final void setContext(ServerContext ctx) { + this.ctx = ctx; + } + + /** + * Starts the service. + */ + public abstract void start(); + +} diff --git a/src/org/apollo/ServiceManager.java b/src/org/apollo/ServiceManager.java new file mode 100644 index 000000000..46fea5923 --- /dev/null +++ b/src/org/apollo/ServiceManager.java @@ -0,0 +1,115 @@ +package org.apollo; + +import java.io.FileInputStream; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Logger; + +import org.apollo.util.xml.XmlNode; +import org.apollo.util.xml.XmlParser; + +/** + * A class which manages {@link Service}s. + * @author Graham + */ +public final class ServiceManager { + + /** + * The logger for this class. + */ + private static final Logger logger = Logger.getLogger(ServiceManager.class.getName()); + + /** + * The service map. + */ + private Map, Service> services = new HashMap, Service>(); + + /** + * Creates and initializes the {@link ServiceManager}. + * @throws Exception if an error occurs. + */ + public ServiceManager() throws Exception { + init(); + } + + /** + * Initializes this service manager. + * @throws Exception if an error occurs. + */ + @SuppressWarnings("unchecked") + private void init() throws Exception { + logger.info("Registering services..."); + + XmlParser parser = new XmlParser(); + XmlNode rootNode; + + InputStream is = new FileInputStream("data/services.xml"); + try { + rootNode = parser.parse(is); + } finally { + is.close(); + } + + if (!rootNode.getName().equals("services")) { + throw new Exception("unexpected name of root node"); + } + + for (XmlNode childNode : rootNode) { + if (!childNode.getName().equals("service")) { + throw new Exception("unexpected name of child node"); + } + + if (!childNode.hasValue()) { + throw new Exception("child node must have a value!"); + } + + Class clazz = (Class) Class.forName(childNode.getValue()); + register((Class) clazz, clazz.newInstance()); + } + } + + /** + * Registers a service. + * @param The type of service. + * @param clazz The service's class. + * @param service The service. + */ + private void register(Class clazz, S service) { + logger.fine("Registering service: " + clazz + "..."); + services.put(clazz, service); + } + + /** + * Gets a service. + * @param The type of service. + * @param clazz The service class. + * @return The service. + */ + @SuppressWarnings("unchecked") + public S getService(Class clazz) { + return (S) services.get(clazz); + } + + /** + * Starts all the services. + */ + public void startAll() { + logger.info("Starting services..."); + for (Service service : services.values()) { + logger.fine("Starting service: " + service.getClass().getName() + "..."); + service.start(); + } + } + + /** + * Sets the context of all services. + * @param ctx The server context. + */ + public void setContext(ServerContext ctx) { + for (Service s : services.values()) { + s.setContext(ctx); + } + } + +} diff --git a/src/org/apollo/fs/FileDescriptor.java b/src/org/apollo/fs/FileDescriptor.java new file mode 100644 index 000000000..212703588 --- /dev/null +++ b/src/org/apollo/fs/FileDescriptor.java @@ -0,0 +1,45 @@ +package org.apollo.fs; + +/** + * A class which points to a file in the cache. + * @author Graham + */ +public final class FileDescriptor { + + /** + * The file type. + */ + private final int type; + + /** + * The file id. + */ + private final int file; + + /** + * Creates the file descriptor. + * @param type The file type. + * @param file The file id. + */ + public FileDescriptor(int type, int file) { + this.type = type; + this.file = file; + } + + /** + * Gets the file type. + * @return The file type. + */ + public int getType() { + return type; + } + + /** + * Gets the file id. + * @return The file id. + */ + public int getFile() { + return file; + } + +} diff --git a/src/org/apollo/fs/FileSystemConstants.java b/src/org/apollo/fs/FileSystemConstants.java new file mode 100644 index 000000000..2c308162b --- /dev/null +++ b/src/org/apollo/fs/FileSystemConstants.java @@ -0,0 +1,46 @@ +package org.apollo.fs; + +/** + * Holds file system related constants. + * @author Graham + */ +public final class FileSystemConstants { + + /** + * The number of caches. + */ + public static final int CACHE_COUNT = 5; + + /** + * The number of archives in cache 0. + */ + public static final int ARCHIVE_COUNT = 9; + + /** + * The size of an index. + */ + public static final int INDEX_SIZE = 6; + + /** + * The size of a header. + */ + public static final int HEADER_SIZE = 8; + + /** + * The size of a chunk. + */ + public static final int CHUNK_SIZE = 512; + + /** + * The size of a block. + */ + public static final int BLOCK_SIZE = HEADER_SIZE + CHUNK_SIZE; + + /** + * Default private constructor to prevent instantiation. + */ + private FileSystemConstants() { + + } + +} diff --git a/src/org/apollo/fs/Index.java b/src/org/apollo/fs/Index.java new file mode 100644 index 000000000..505034f16 --- /dev/null +++ b/src/org/apollo/fs/Index.java @@ -0,0 +1,62 @@ +package org.apollo.fs; + +/** + * An {@link Index} points to a file in the {@code main_file_cache.dat} file. + * @author Graham + */ +public final class Index { + + /** + * Decodes a buffer into an index. + * @param buffer The buffer. + * @return The decoded {@link Index}. + * @throws IllegalArgumentException if the buffer length is invalid. + */ + public static Index decode(byte[] buffer) { + if (buffer.length != FileSystemConstants.INDEX_SIZE) { + throw new IllegalArgumentException("Incorrect buffer length."); + } + + int size = ((buffer[0] & 0xFF) << 16) | ((buffer[1] & 0xFF) << 8) | (buffer[2] & 0xFF); + int block = ((buffer[3] & 0xFF) << 16) | ((buffer[4] & 0xFF) << 8) | (buffer[5] & 0xFF); + + return new Index(size, block); + } + + /** + * The size of the file. + */ + private final int size; + + /** + * The first block of the file. + */ + private final int block; + + /** + * Creates the index. + * @param size The size of the file. + * @param block The first block of the file. + */ + public Index(int size, int block) { + this.size = size; + this.block = block; + } + + /** + * Gets the size of the file. + * @return The size of the file. + */ + public int getSize() { + return size; + } + + /** + * Gets the first block of the file. + * @return The first block of the file. + */ + public int getBlock() { + return block; + } + +} diff --git a/src/org/apollo/fs/IndexedFileSystem.java b/src/org/apollo/fs/IndexedFileSystem.java new file mode 100644 index 000000000..c8110e050 --- /dev/null +++ b/src/org/apollo/fs/IndexedFileSystem.java @@ -0,0 +1,291 @@ +package org.apollo.fs; + +import java.io.Closeable; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.util.zip.CRC32; + +/** + * A file system based on top of the operating system's file system. It + * consists of a data file and index files. Index files point to blocks in the + * data file, which contains the actual data. + * @author Graham + */ +public final class IndexedFileSystem implements Closeable { + + /** + * Read only flag. + */ + private final boolean readOnly; + + /** + * The index files. + */ + private RandomAccessFile[] indices = new RandomAccessFile[256]; + + /** + * The data file. + */ + private RandomAccessFile data; + + /** + * The cached CRC table. + */ + private ByteBuffer crcTable; + + /** + * Creates the file system with the specified base directory. + * @param base The base directory. + * @param readOnly A flag indicating if the file system will be read only. + * @throws Exception if the file system is invalid. + */ + public IndexedFileSystem(File base, boolean readOnly) throws Exception { + this.readOnly = readOnly; + detectLayout(base); + } + + /** + * Checks if this {@link IndexedFileSystem} is read only. + * @return {@code true} if so, {@code false} if not. + */ + public boolean isReadOnly() { + return readOnly; + } + + /** + * Automatically detect the layout of the specified directory. + * @param base The base directory. + * @throws Exception if the file system is invalid. + */ + private void detectLayout(File base) throws Exception { + int indexCount = 0; + for (int index = 0; index < indices.length; index++) { + File f = new File(base.getAbsolutePath() + "/main_file_cache.idx" + index); + if (f.exists() && !f.isDirectory()) { + indexCount++; + indices[index] = new RandomAccessFile(f, readOnly ? "r" : "rw"); + } + } + if (indexCount <= 0) { + throw new Exception("No index file(s) present"); + } + + File oldEngineData = new File(base.getAbsolutePath() + "/main_file_cache.dat"); + File newEngineData = new File(base.getAbsolutePath() + "/main_file_cache.dat2"); + if (oldEngineData.exists() && !oldEngineData.isDirectory()) { + data = new RandomAccessFile(oldEngineData, readOnly ? "r" : "rw"); + } else if (newEngineData.exists() && !oldEngineData.isDirectory()) { + data = new RandomAccessFile(newEngineData, readOnly ? "r" : "rw"); + } else { + throw new Exception("No data file present"); + } + } + + /** + * Gets the index of a file. + * @param fd The {@link FileDescriptor} which points to the file. + * @return The {@link Index}. + * @throws IOException if an I/O error occurs. + */ + private Index getIndex(FileDescriptor fd) throws IOException { + int index = fd.getType(); + if (index < 0 || index >= indices.length) { + throw new IndexOutOfBoundsException(); + } + + byte[] buffer = new byte[FileSystemConstants.INDEX_SIZE]; + RandomAccessFile indexFile = indices[index]; + synchronized (indexFile) { + long ptr = (long) fd.getFile() * (long) FileSystemConstants.INDEX_SIZE; + if (ptr >= 0 && indexFile.length() >= (ptr + FileSystemConstants.INDEX_SIZE)) { + indexFile.seek(ptr); + indexFile.readFully(buffer); + } else { + throw new FileNotFoundException(); + } + } + + return Index.decode(buffer); + } + + /** + * Gets the number of files with the specified type. + * @param type The type. + * @return The number of files. + * @throws IOException if an I/O error occurs. + */ + private int getFileCount(int type) throws IOException { + if (type < 0 || type >= indices.length) { + throw new IndexOutOfBoundsException(); + } + + RandomAccessFile indexFile = indices[type]; + synchronized (indexFile) { + return (int) (indexFile.length() / FileSystemConstants.INDEX_SIZE); + } + } + + /** + * Gets the CRC table. + * @return The CRC table. + * @throws IOException if an I/O erorr occurs. + */ + public ByteBuffer getCrcTable() throws IOException { + if (readOnly) { + synchronized (this) { + if (crcTable != null) { + return crcTable.duplicate(); + } + } + + // the number of archives + int archives = getFileCount(0); + + // the hash + int hash = 1234; + + // the CRCs + int[] crcs = new int[archives]; + + // calculate the CRCs + CRC32 crc32 = new CRC32(); + for (int i = 1; i < crcs.length; i++) { + crc32.reset(); + + ByteBuffer bb = getFile(0, i); + byte[] bytes = new byte[bb.remaining()]; + bb.get(bytes, 0, bytes.length); + crc32.update(bytes, 0, bytes.length); + + crcs[i] = (int) crc32.getValue(); + } + + // hash the CRCs and place them in the buffer + ByteBuffer buf = ByteBuffer.allocate(crcs.length * 4 + 4); + for (int i = 0; i < crcs.length; i++) { + hash = (hash << 1) + crcs[i]; + buf.putInt(crcs[i]); + } + + // place the hash into the buffer + buf.putInt(hash); + buf.flip(); + + synchronized (this) { + crcTable = buf.asReadOnlyBuffer(); + return crcTable.duplicate(); + } + } else { + throw new IOException("cannot get CRC table from a writable file system"); + } + } + + /** + * Gets a file. + * @param type The file type. + * @param file The file id. + * @return A {@link ByteBuffer} which contains the contents of the file. + * @throws IOException if an I/O error occurs. + */ + public ByteBuffer getFile(int type, int file) throws IOException { + return getFile(new FileDescriptor(type, file)); + } + + /** + * Gets a file. + * @param fd The {@link FileDescriptor} which points to the file. + * @return A {@link ByteBuffer} which contains the contents of the file. + * @throws IOException if an I/O error occurs. + */ + public ByteBuffer getFile(FileDescriptor fd) throws IOException { + Index index = getIndex(fd); + ByteBuffer buffer = ByteBuffer.allocate(index.getSize()); + + // calculate some initial values + long ptr = (long) index.getBlock() * (long) FileSystemConstants.BLOCK_SIZE; + int read = 0; + int size = index.getSize(); + int blocks = size / FileSystemConstants.CHUNK_SIZE; + if (size % FileSystemConstants.CHUNK_SIZE != 0) { + blocks++; + } + + for (int i = 0; i < blocks; i++) { + + // read header + byte[] header = new byte[FileSystemConstants.HEADER_SIZE]; + synchronized (data) { + data.seek(ptr); + data.readFully(header); + } + + // increment pointers + ptr += FileSystemConstants.HEADER_SIZE; + + // parse header + int nextFile = ((header[0] & 0xFF) << 8) | (header[1] & 0xFF); + int curChunk = ((header[2] & 0xFF) << 8) | (header[3] & 0xFF); + int nextBlock = ((header[4] & 0xFF) << 16) | ((header[5] & 0xFF) << 8) | (header[6] & 0xFF); + int nextType = header[7] & 0xFF; + + // check expected chunk id is correct + if (i != curChunk) { + throw new IOException("Chunk id mismatch."); + } + + // calculate how much we can read + int chunkSize = size - read; + if (chunkSize > FileSystemConstants.CHUNK_SIZE) { + chunkSize = FileSystemConstants.CHUNK_SIZE; + } + + // read the next chunk and put it in the buffer + byte[] chunk = new byte[chunkSize]; + synchronized (data) { + data.seek(ptr); + data.readFully(chunk); + } + buffer.put(chunk); + + // increment pointers + read += chunkSize; + ptr = (long) nextBlock * (long) FileSystemConstants.BLOCK_SIZE; + + // if we still have more data to read, check the validity of the + // header + if (size > read) { + if (nextType != (fd.getType() + 1)) { + throw new IOException("File type mismatch."); + } + + if (nextFile != fd.getFile()) { + throw new IOException("File id mismatch."); + } + } + } + + buffer.flip(); + return buffer; + } + + @Override + public void close() throws IOException { + if (data != null) { + synchronized (data) { + data.close(); + } + } + + for (RandomAccessFile index : indices) { + if (index != null) { + synchronized (index) { + index.close(); + } + } + } + } + +} diff --git a/src/org/apollo/fs/archive/Archive.java b/src/org/apollo/fs/archive/Archive.java new file mode 100644 index 000000000..a8e999286 --- /dev/null +++ b/src/org/apollo/fs/archive/Archive.java @@ -0,0 +1,97 @@ +package org.apollo.fs.archive; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.ByteBuffer; + +import org.apollo.util.ByteBufferUtil; +import org.apollo.util.CompressionUtil; + +/** + * Represents an archive. + * @author Graham + */ +public final class Archive { + + /** + * Decodes the archive in the specified buffer. + * @param buffer The buffer. + * @return The archive. + * @throws IOException if an I/O error occurs. + */ + public static Archive decode(ByteBuffer buffer) throws IOException { + int extractedSize = ByteBufferUtil.readUnsignedTriByte(buffer); + int size = ByteBufferUtil.readUnsignedTriByte(buffer); + boolean extracted = false; + + if (size != extractedSize) { + byte[] compressed = new byte[size]; + byte[] uncompressed = new byte[extractedSize]; + buffer.get(compressed); + CompressionUtil.unbzip2(compressed, uncompressed); + buffer = ByteBuffer.wrap(uncompressed); + extracted = true; + } + + int entries = buffer.getShort() & 0xFFFF; + int[] identifiers = new int[entries]; + int[] extractedSizes = new int[entries]; + int[] sizes = new int[entries]; + + for (int i = 0; i < entries; i++) { + identifiers[i] = buffer.getInt(); + extractedSizes[i] = ByteBufferUtil.readUnsignedTriByte(buffer); + sizes[i] = ByteBufferUtil.readUnsignedTriByte(buffer); + } + + ArchiveEntry[] entry = new ArchiveEntry[entries]; + + for (int i = 0; i < entries; i++) { + ByteBuffer entryBuffer = ByteBuffer.allocate(extractedSizes[i]); + if (!extracted) { + byte[] compressed = new byte[sizes[i]]; + byte[] uncompressed = new byte[extractedSizes[i]]; + buffer.get(compressed); + CompressionUtil.unbzip2(compressed, uncompressed); + entryBuffer = ByteBuffer.wrap(uncompressed); + } + entry[i] = new ArchiveEntry(identifiers[i], entryBuffer); + } + + return new Archive(entry); + } + + /** + * The entries in this archive. + */ + private final ArchiveEntry[] entries; + + /** + * Creates a new archive. + * @param entries The entries in this archive. + */ + public Archive(ArchiveEntry[] entries) { + this.entries = entries; + } + + /** + * Gets an entry by its name. + * @param name The name. + * @return The entry. + * @throws FileNotFoundException if the file could not be found. + */ + public ArchiveEntry getEntry(String name) throws FileNotFoundException { + int hash = 0; + name = name.toUpperCase(); + for (int i = 0; i < name.length(); i++) { + hash = (hash * 61 + name.charAt(i)) - 32; + } + for (ArchiveEntry entry : entries) { + if (entry.getIdentifier() == hash) { + return entry; + } + } + throw new FileNotFoundException(); + } + +} diff --git a/src/org/apollo/fs/archive/ArchiveEntry.java b/src/org/apollo/fs/archive/ArchiveEntry.java new file mode 100644 index 000000000..1688483cd --- /dev/null +++ b/src/org/apollo/fs/archive/ArchiveEntry.java @@ -0,0 +1,47 @@ +package org.apollo.fs.archive; + +import java.nio.ByteBuffer; + +/** + * Represents a single entry in an {@link Archive}. + * @author Graham + */ +public final class ArchiveEntry { + + /** + * The identifier of this entry. + */ + private final int identifier; + + /** + * The buffer of this entry. + */ + private final ByteBuffer buffer; + + /** + * Creates a new archive entry. + * @param identifier The identifier. + * @param buffer The buffer. + */ + public ArchiveEntry(int identifier, ByteBuffer buffer) { + this.identifier = identifier; + this.buffer = buffer.asReadOnlyBuffer(); + } + + /** + * Gets the identifier of this entry. + * @return The identifier of this entry. + */ + public int getIdentifier() { + return identifier; + } + + /** + * Gets the buffer of this entry. + * @return This buffer of this entry. + */ + public ByteBuffer getBuffer() { + return buffer.duplicate(); + } + +} diff --git a/src/org/apollo/fs/archive/package-info.java b/src/org/apollo/fs/archive/package-info.java new file mode 100644 index 000000000..0d21259ce --- /dev/null +++ b/src/org/apollo/fs/archive/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains classes which deal with archives. + */ +package org.apollo.fs.archive; diff --git a/src/org/apollo/fs/package-info.java b/src/org/apollo/fs/package-info.java new file mode 100644 index 000000000..14c2a3bd8 --- /dev/null +++ b/src/org/apollo/fs/package-info.java @@ -0,0 +1,5 @@ +/** + * Contains classes which deal with the file system that the client uses to + * store game data files. + */ +package org.apollo.fs; diff --git a/src/org/apollo/fs/parser/ItemDefinitionParser.java b/src/org/apollo/fs/parser/ItemDefinitionParser.java new file mode 100644 index 000000000..bfc586920 --- /dev/null +++ b/src/org/apollo/fs/parser/ItemDefinitionParser.java @@ -0,0 +1,186 @@ +package org.apollo.fs.parser; + +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.ItemDefinition; +import org.apollo.util.ByteBufferUtil; + +/** + * A class which parses item definitions. + * @author Graham + */ +public final class ItemDefinitionParser { + + /** + * The indexed file system. + */ + private final IndexedFileSystem fs; + + /** + * Creates the item definition parser. + * @param fs The indexed file system. + */ + public ItemDefinitionParser(IndexedFileSystem fs) { + this.fs = fs; + } + + /** + * Parses the item definitions. + * @return The item definitions. + * @throws IOException if an I/O error occurs. + */ + public ItemDefinition[] parse() throws IOException { + Archive config = Archive.decode(fs.getFile(0, 2)); + ByteBuffer dat = config.getEntry("obj.dat").getBuffer(); + ByteBuffer idx = config.getEntry("obj.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(); + } + + ItemDefinition[] defs = new ItemDefinition[count]; + for (int i = 0; i < count; i++) { + dat.position(indices[i]); + defs[i] = parseDefinition(i, dat); + } + + return defs; + } + + /** + * Parses a single definition. + * @param id The item's id. + * @param buffer The buffer. + * @return The definition. + */ + private ItemDefinition parseDefinition(int id, ByteBuffer buffer) { + ItemDefinition def = new ItemDefinition(id); + + while (true) { + int code = buffer.get() & 0xFF; + + if (code == 0) { + return def; + } else if (code == 1) { + @SuppressWarnings("unused") + int modelId = buffer.getShort() & 0xFFFF; + } else if (code == 2) { + def.setName(ByteBufferUtil.readString(buffer)); + } else if (code == 3) { + def.setDescription(ByteBufferUtil.readString(buffer)); + } else if (code == 4) { + @SuppressWarnings("unused") + int modelScale = buffer.getShort() & 0xFFFF; + } else if (code == 5) { + @SuppressWarnings("unused") + int modelRotationX = buffer.getShort() & 0xFFFF; + } else if (code == 6) { + @SuppressWarnings("unused") + int modelRotationY = buffer.getShort() & 0xFFFF; + } else if (code == 7) { + @SuppressWarnings("unused") + int modelTransformationX = buffer.getShort(); + } else if (code == 8) { + @SuppressWarnings("unused") + int modelTransformationY = buffer.getShort(); + } else if (code == 10) { + @SuppressWarnings("unused") + int unknown = buffer.getShort() & 0xFFFF; + } else if (code == 11) { + def.setStackable(true); + } else if (code == 12) { + def.setValue(buffer.getInt()); + } else if (code == 16) { + def.setMembersOnly(true); + } else if (code == 23) { + @SuppressWarnings("unused") + int unknownShort = buffer.getShort() & 0xFFFF; + @SuppressWarnings("unused") + int unknownByte = buffer.get(); + } else if (code == 24) { + @SuppressWarnings("unused") + int unknownShort = buffer.getShort() & 0xFFFF; + } else if (code == 25) { + @SuppressWarnings("unused") + int unknownShort = buffer.getShort() & 0xFFFF; + @SuppressWarnings("unused") + int unknownByte = buffer.get(); + } else if (code == 26) { + @SuppressWarnings("unused") + int unknownShort = buffer.getShort() & 0xFFFF; + } else if (code >= 30 && code < 35) { + String str = ByteBufferUtil.readString(buffer); + if (str.equalsIgnoreCase("hidden")) { + str = null; + } + def.setGroundAction(code - 30, str); + } else if (code >= 35 && code < 40) { + String str = ByteBufferUtil.readString(buffer); + def.setInventoryAction(code - 35, str); + } else if (code == 40) { + int colorCount = buffer.get() & 0xFF; + for (int i = 0; i < colorCount; i++) { + @SuppressWarnings("unused") + int oldColor = buffer.getShort() & 0xFFFF; + @SuppressWarnings("unused") + int newColor = buffer.getShort() & 0xFFFF; + } + } else if (code == 78) { + @SuppressWarnings("unused") + int unknown = buffer.getShort() & 0xFFFF; + } else if (code == 79) { + @SuppressWarnings("unused") + int unknown = buffer.getShort() & 0xFFFF; + } else if (code == 90) { + @SuppressWarnings("unused") + int unknown = buffer.getShort() & 0xFFFF; + } else if (code == 91) { + @SuppressWarnings("unused") + int unknown = buffer.getShort() & 0xFFFF; + } else if (code == 92) { + @SuppressWarnings("unused") + int unknown = buffer.getShort() & 0xFFFF; + } else if (code == 93) { + @SuppressWarnings("unused") + int unknown = buffer.getShort() & 0xFFFF; + } else if (code == 95) { + @SuppressWarnings("unused") + int unknown = buffer.getShort() & 0xFFFF; + } else if (code == 97) { + int noteInfoId = buffer.getShort() & 0xFFFF; + def.setNoteInfoId(noteInfoId); + } else if (code == 98) { + int noteGraphicId = buffer.getShort() & 0xFFFF; + def.setNoteGraphicId(noteGraphicId); + } else if (code >= 100 && code < 110) { + @SuppressWarnings("unused") + int stackId = buffer.getShort() & 0xFFFF; + @SuppressWarnings("unused") + int stackAmount = buffer.getShort() & 0xFFFF; + } else if (code == 110) { + @SuppressWarnings("unused") + int unknown = buffer.getShort() & 0xFFFF; + } else if (code == 111) { + @SuppressWarnings("unused") + int unknown = buffer.getShort() & 0xFFFF; + } else if (code == 112) { + @SuppressWarnings("unused") + int unknown = buffer.getShort() & 0xFFFF; + } else if (code == 114) { + @SuppressWarnings("unused") + int unknown = buffer.getShort() * 5; + } else if (code == 115) { + @SuppressWarnings("unused") + int team = buffer.get() & 0xFF; + } + } + } + +} diff --git a/src/org/apollo/fs/parser/package-info.java b/src/org/apollo/fs/parser/package-info.java new file mode 100644 index 000000000..c08852a93 --- /dev/null +++ b/src/org/apollo/fs/parser/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains classes which parse files within the game's cache. + */ +package org.apollo.fs.parser; diff --git a/src/org/apollo/game/GameConstants.java b/src/org/apollo/game/GameConstants.java new file mode 100644 index 000000000..576d3e0ff --- /dev/null +++ b/src/org/apollo/game/GameConstants.java @@ -0,0 +1,26 @@ +package org.apollo.game; + +/** + * Contains game-related constants. + * @author Graham + */ +public final class GameConstants { + + /** + * The delay between consecutive pulses, in milliseconds. + */ + public static final int PULSE_DELAY = 600; + + /** + * The maximum events per pulse per session. + */ + public static final int EVENTS_PER_PULSE = 10; + + /** + * Default private constructor to prevent instantiation by other classes. + */ + private GameConstants() { + + } + +} diff --git a/src/org/apollo/game/GamePulseHandler.java b/src/org/apollo/game/GamePulseHandler.java new file mode 100644 index 000000000..aeea5b220 --- /dev/null +++ b/src/org/apollo/game/GamePulseHandler.java @@ -0,0 +1,39 @@ +package org.apollo.game; + +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A class which handles the logic for each pulse of the {@link GameService}. + * @author Graham + */ +public final class GamePulseHandler implements Runnable { + + /** + * The logger for this class. + */ + private static final Logger logger = Logger.getLogger(GamePulseHandler.class.getName()); + + /** + * The {@link GameService}. + */ + private final GameService service; + + /** + * Creates the game pulse handler object. + * @param service The {@link GameService}. + */ + GamePulseHandler(GameService service) { + this.service = service; + } + + @Override + public void run() { + try { + service.pulse(); + } catch (Throwable t) { + logger.log(Level.SEVERE, "Exception during pulse.", t); + } + } + +} diff --git a/src/org/apollo/game/GameService.java b/src/org/apollo/game/GameService.java new file mode 100644 index 000000000..458781381 --- /dev/null +++ b/src/org/apollo/game/GameService.java @@ -0,0 +1,174 @@ +package org.apollo.game; + +import java.io.FileInputStream; +import java.io.InputStream; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import org.apollo.Service; +import org.apollo.game.event.handler.chain.EventHandlerChainGroup; +import org.apollo.game.model.Player; +import org.apollo.game.model.World; +import org.apollo.game.model.World.RegistrationStatus; +import org.apollo.game.sync.ClientSynchronizer; +import org.apollo.io.EventHandlerChainParser; +import org.apollo.login.LoginService; +import org.apollo.net.session.GameSession; +import org.apollo.util.NamedThreadFactory; +import org.apollo.util.xml.XmlNode; +import org.apollo.util.xml.XmlParser; + +/** + * The {@link GameService} class schedules and manages the execution of the + * {@link GamePulseHandler} class. + * @author Graham + */ +public final class GameService extends Service { + + /** + * The number of times to unregister players per cycle. This is to ensure + * the saving threads don't get swamped with requests and slow everything + * down. + */ + private static final int UNREGISTERS_PER_CYCLE = 50; + + /** + * The scheduled executor service. + */ + private final ScheduledExecutorService scheduledExecutor = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("GameService")); + + /** + * A queue of players to remove. + */ + private final Queue oldPlayers = new ConcurrentLinkedQueue(); + + /** + * The {@link EventHandlerChainGroup}. + */ + private EventHandlerChainGroup chainGroup; + + /** + * The {@link ClientSynchronizer}. + */ + private ClientSynchronizer synchronizer; + + /** + * Creates the game service. + * @throws Exception if an error occurs during initialization. + */ + public GameService() throws Exception { + init(); + } + + /** + * Gets the event handler chains. + * @return The event handler chains. + */ + public EventHandlerChainGroup getEventHandlerChains() { + return chainGroup; + } + + /** + * Initializes the game service. + * @throws Exception if an error occurs. + */ + private void init() throws Exception { + InputStream is = new FileInputStream("data/events.xml"); + try { + EventHandlerChainParser chainGroupParser = new EventHandlerChainParser(is); + chainGroup = chainGroupParser.parse(); + } finally { + is.close(); + } + + is = new FileInputStream("data/synchronizer.xml"); + try { + XmlParser parser = new XmlParser(); + XmlNode rootNode = parser.parse(is); + + if (!rootNode.getName().equals("synchronizer")) { + throw new Exception("Invalid root node name."); + } + + XmlNode activeNode = rootNode.getChild("active"); + if (activeNode == null || !activeNode.hasValue()) { + throw new Exception("No active node/value."); + } + + Class clazz = Class.forName(activeNode.getValue()); + synchronizer = (ClientSynchronizer) clazz.newInstance(); + } finally { + is.close(); + } + } + + /** + * Starts the game service. + */ + @Override + public void start() { + scheduledExecutor.scheduleAtFixedRate(new GamePulseHandler(this), GameConstants.PULSE_DELAY, GameConstants.PULSE_DELAY, TimeUnit.MILLISECONDS); + } + + /** + * Called every pulse. + */ + public void pulse() { + synchronized (this) { + LoginService loginService = getContext().getService(LoginService.class); + World world = World.getWorld(); + + int unregistered = 0; + Player old; + while (unregistered < UNREGISTERS_PER_CYCLE && (old = oldPlayers.poll()) != null) { + loginService.submitSaveRequest(old.getSession(), old); + unregistered++; + } + + for (Player p : world.getPlayerRepository()) { + GameSession session = p.getSession(); + if (session != null) { + session.handlePendingEvents(chainGroup); + } + } + + world.pulse(); + + synchronizer.synchronize(); + } + } + + /** + * Registers a player (may block!). + * @param player The player. + * @return A {@link RegistrationStatus}. + */ + public RegistrationStatus registerPlayer(Player player) { + synchronized (this) { + return World.getWorld().register(player); + } + } + + /** + * Unregisters a player. Returns immediately. The player is unregistered + * at the start of the next cycle. + * @param player The player. + */ + public void unregisterPlayer(Player player) { + oldPlayers.add(player); + } + + /** + * Finalizes the unregistration of a player. + * @param player The player. + */ + public void finalizePlayerUnregistration(Player player) { + synchronized (this) { + World.getWorld().unregister(player); + } + } + +} diff --git a/src/org/apollo/game/action/Action.java b/src/org/apollo/game/action/Action.java new file mode 100644 index 000000000..00370f23d --- /dev/null +++ b/src/org/apollo/game/action/Action.java @@ -0,0 +1,58 @@ +package org.apollo.game.action; + +import org.apollo.game.model.Character; +import org.apollo.game.scheduling.ScheduledTask; + +/** + * An action is a specialised {@link ScheduledTask} which is specific to a + * character. + *

+ * ALL actions MUST implement the + * {@link #equals(Object)} method. This is to check if two actions are + * identical: if they are, then the new action does not replace the old one (so + * spam/accidental clicking won't cancel your action, and start another from + * scratch). + * @author Graham + */ +public abstract class Action extends ScheduledTask { + + /** + * The character performing the action. + */ + private final T character; + + /** + * A flag indicating if this action is stopping. + */ + private boolean stopping = false; + + /** + * Creates a new action. + * @param delay The delay in pulses. + * @param immediate A flag indicating if the action should happen + * immediately. + * @param character The character performing the action. + */ + public Action(int delay, boolean immediate, T character) { + super(delay, immediate); + this.character = character; + } + + /** + * Gets the character which performed the action. + * @return The character. + */ + public T getCharacter() { + return character; + } + + @Override + public void stop() { + super.stop(); + if (!stopping) { + stopping = true; + character.stopAction(); + } + } + +} diff --git a/src/org/apollo/game/action/DistancedAction.java b/src/org/apollo/game/action/DistancedAction.java new file mode 100644 index 000000000..60d098e99 --- /dev/null +++ b/src/org/apollo/game/action/DistancedAction.java @@ -0,0 +1,77 @@ +package org.apollo.game.action; + +import org.apollo.game.model.Character; +import org.apollo.game.model.Position; + +/** + * An @{link Action} which fires when a distance requirement is met. + * @author Blake + * @author Graham + */ +public abstract class DistancedAction extends Action { + + /** + * The position to distance check with. + */ + private final Position position; + + /** + * The minimum distance before the action fires. + */ + private final int distance; + + /** + * The delay once the threshold is reached. + */ + private final int delay; + + /** + * A flag indicating if this action fires immediately after the threshold + * is reached. + */ + private final boolean immediate; + + /** + * A flag indicating if the distance has been reached yet. + */ + private boolean reached = false; + + /** + * Creates a new DistancedAction. + * @param delay The delay between executions once the distance threshold is + * reached. + * @param immediate Whether or not this action fires immediately after the + * distance threshold is reached. + * @param character The character. + * @param position The position. + * @param distance The distance. + */ + public DistancedAction(int delay, boolean immediate, T character, Position position, int distance) { + super(0, true, character); + this.position = position; + this.distance = distance; + this.delay = delay; + this.immediate = immediate; + } + + @Override + public void execute() { + if (reached) { + // some actions (e.g. agility) will cause the player to move away again + // so we don't check once the player got close enough once + executeAction(); + } else if (getCharacter().getPosition().getDistance(position) <= distance) { + reached = true; + setDelay(delay); + if (immediate) { // TODO: required? + executeAction(); + } + } + } + + /** + * Executes the actual action. Called when the distance requirement is met. + */ + public abstract void executeAction(); + +} diff --git a/src/org/apollo/game/action/package-info.java b/src/org/apollo/game/action/package-info.java new file mode 100644 index 000000000..3c36cc53d --- /dev/null +++ b/src/org/apollo/game/action/package-info.java @@ -0,0 +1,5 @@ +/** + * Contains classes related to actions, specialised scheduled tasks which + * characters perform. + */ +package org.apollo.game.action; diff --git a/src/org/apollo/game/command/Command.java b/src/org/apollo/game/command/Command.java new file mode 100644 index 000000000..3365d8323 --- /dev/null +++ b/src/org/apollo/game/command/Command.java @@ -0,0 +1,45 @@ +package org.apollo.game.command; + +/** + * Represents a command. + * @author Graham + */ +public final class Command { + + /** + * The name of the command. + */ + private final String name; + + /** + * The command's arguments. + */ + private final String[] arguments; + + /** + * Creates the command. + * @param name The name of the command. + * @param arguments The command's arguments. + */ + public Command(String name, String[] arguments) { + this.name = name; + this.arguments = arguments; + } + + /** + * Gets the name of the command. + * @return The name of the command. + */ + public String getName() { + return name; + } + + /** + * Gets the command's arguments. + * @return The command's arguments. + */ + public String[] getArguments() { + return arguments; + } + +} diff --git a/src/org/apollo/game/command/CommandDispatcher.java b/src/org/apollo/game/command/CommandDispatcher.java new file mode 100644 index 000000000..60f094b4a --- /dev/null +++ b/src/org/apollo/game/command/CommandDispatcher.java @@ -0,0 +1,49 @@ +package org.apollo.game.command; + +import java.util.HashMap; +import java.util.Map; + +import org.apollo.game.model.Player; + +/** + * A class which dispatches {@link Command}s to {@link CommandListener}s. + * @author Graham + */ +public final class CommandDispatcher { + + /** + * A map of event listeners. + */ + private final Map listeners = new HashMap(); + + /** + * Creates the command dispatcher and registers a listener for the credits + * command. + */ + public CommandDispatcher() { + // not in a plugin so it is harder for people to remove! + listeners.put("credits", new CreditsCommandListener()); + } + + /** + * Registers a listener with the + * @param command The command's name. + * @param listener The listener. + */ + public void register(String command, CommandListener listener) { + listeners.put(command.toLowerCase(), listener); + } + + /** + * Dispatches a command to the appropriate listener. + * @param player The player. + * @param command The command. + */ + public void dispatch(Player player, Command command) { + CommandListener listener = listeners.get(command.getName().toLowerCase()); + if (listener != null) { + listener.execute(player, command); + } + } + +} diff --git a/src/org/apollo/game/command/CommandListener.java b/src/org/apollo/game/command/CommandListener.java new file mode 100644 index 000000000..6b3dd3e9e --- /dev/null +++ b/src/org/apollo/game/command/CommandListener.java @@ -0,0 +1,19 @@ +package org.apollo.game.command; + +import org.apollo.game.model.Player; + +/** + * An interface which should be implemented by classes to listen to + * {@link Command}s. + * @author Graham + */ +public interface CommandListener { + + /** + * Executes the action for this command. + * @param player The player. + * @param command The command. + */ + public void execute(Player player, Command command); + +} diff --git a/src/org/apollo/game/command/CreditsCommandListener.java b/src/org/apollo/game/command/CreditsCommandListener.java new file mode 100644 index 000000000..0016243e6 --- /dev/null +++ b/src/org/apollo/game/command/CreditsCommandListener.java @@ -0,0 +1,59 @@ +package org.apollo.game.command; + +import java.util.Iterator; + +import org.apollo.game.event.impl.SetInterfaceTextEvent; +import org.apollo.game.model.Player; +import org.apollo.game.model.World; +import org.apollo.game.model.inter.quest.QuestConstants; +import org.apollo.util.plugin.PluginManager; + +/** + * Implements a {@code ::credits} command that lists the authors of all plugins + * used in the server. + * @author Graham + */ +public final class CreditsCommandListener implements CommandListener { + + /* + * If you are considering removing this command, please bear in mind that + * Apollo took several people thousands of hours to create. We released it + * to the world for free and it isn't much to ask to leave this command in. + * It isn't very obtrusive and gives us some well-deserved recognition for + * the work we have done. Thank you! + * + * The list of authors is generated from the plugin manager. If you create + * a custom plugin, make sure you add your name to the plugin.xml file and + * it'll appear here automatically! + */ + + @Override + public void execute(Player player, Command command) { + PluginManager mgr = World.getWorld().getPluginManager(); + Iterator it = mgr.createAuthorsIterator(); + + int pos = 0; + player.send(new SetInterfaceTextEvent(QuestConstants.QUEST_TEXT[pos++], "@dre@Apollo")); + player.send(new SetInterfaceTextEvent(QuestConstants.QUEST_TEXT[pos++], "@dre@Introduction")); + player.send(new SetInterfaceTextEvent(QuestConstants.QUEST_TEXT[pos++], "")); + player.send(new SetInterfaceTextEvent(QuestConstants.QUEST_TEXT[pos++], "This server is based on Apollo, a lightweight, fast, secure")); + player.send(new SetInterfaceTextEvent(QuestConstants.QUEST_TEXT[pos++], "and open-source RuneScape emulator. For more")); + player.send(new SetInterfaceTextEvent(QuestConstants.QUEST_TEXT[pos++], "information about Apollo, visit the website at:")); + player.send(new SetInterfaceTextEvent(QuestConstants.QUEST_TEXT[pos++], "@dbl@https://github.com/apollo-rsps/apollo")); + player.send(new SetInterfaceTextEvent(QuestConstants.QUEST_TEXT[pos++], "")); + player.send(new SetInterfaceTextEvent(QuestConstants.QUEST_TEXT[pos++], "Apollo is released under the terms of the ISC")); + player.send(new SetInterfaceTextEvent(QuestConstants.QUEST_TEXT[pos++], "license, details can be found in the root folder of the ")); + player.send(new SetInterfaceTextEvent(QuestConstants.QUEST_TEXT[pos++], "Apollo distribution.")); + player.send(new SetInterfaceTextEvent(QuestConstants.QUEST_TEXT[pos++], "")); + player.send(new SetInterfaceTextEvent(QuestConstants.QUEST_TEXT[pos++], "@dre@Credits")); + player.send(new SetInterfaceTextEvent(QuestConstants.QUEST_TEXT[pos++], "")); + + for (; pos < QuestConstants.QUEST_TEXT.length; pos++) { + String text = it.hasNext() ? it.next() : ""; + player.send(new SetInterfaceTextEvent(QuestConstants.QUEST_TEXT[pos], text)); + } + + player.getInterfaceSet().openWindow(QuestConstants.QUEST_INTERFACE); + } + +} diff --git a/src/org/apollo/game/command/PrivilegedCommandListener.java b/src/org/apollo/game/command/PrivilegedCommandListener.java new file mode 100644 index 000000000..dfbc931d9 --- /dev/null +++ b/src/org/apollo/game/command/PrivilegedCommandListener.java @@ -0,0 +1,40 @@ +package org.apollo.game.command; + +import org.apollo.game.model.Player; +import org.apollo.game.model.Player.PrivilegeLevel; + +/** + * A {@link CommandListener} which checks the {@link PrivilegeLevel} of the + * {@link Player} executing the command. + * @author Graham + */ +public abstract class PrivilegedCommandListener implements CommandListener { + + /** + * The minimum privilege level. + */ + private final PrivilegeLevel level; + + /** + * Creates the privileged command listener with the specified minimum level. + * @param level The minimum privilege level. + */ + public PrivilegedCommandListener(PrivilegeLevel level) { + this.level = level; + } + + /** + * Executes a privileged command. + * @param player The player. + * @param command The command. + */ + public abstract void executePrivileged(Player player, Command command); + + @Override + public final void execute(Player player, Command command) { + if (player.getPrivilegeLevel().toInteger() >= level.toInteger()) { + executePrivileged(player, command); + } + } + +} diff --git a/src/org/apollo/game/command/package-info.java b/src/org/apollo/game/command/package-info.java new file mode 100644 index 000000000..ad8d81231 --- /dev/null +++ b/src/org/apollo/game/command/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains classes related to in-game commands. + */ +package org.apollo.game.command; diff --git a/src/org/apollo/game/event/Event.java b/src/org/apollo/game/event/Event.java new file mode 100644 index 000000000..91b9e2ea5 --- /dev/null +++ b/src/org/apollo/game/event/Event.java @@ -0,0 +1,9 @@ +package org.apollo.game.event; + +/** + * Represents an event that can occur in the game world. + * @author Graham + */ +public abstract class Event { + +} diff --git a/src/org/apollo/game/event/handler/EventHandler.java b/src/org/apollo/game/event/handler/EventHandler.java new file mode 100644 index 000000000..c4e5b236b --- /dev/null +++ b/src/org/apollo/game/event/handler/EventHandler.java @@ -0,0 +1,21 @@ +package org.apollo.game.event.handler; + +import org.apollo.game.event.Event; +import org.apollo.game.model.Player; + +/** + * A class which handles events. + * @author Graham + * @param The type of event this class handles. + */ +public abstract class EventHandler { + + /** + * Handles an event. + * @param ctx The context. + * @param player The player. + * @param event The event. + */ + public abstract void handle(EventHandlerContext ctx, Player player, E event); + +} diff --git a/src/org/apollo/game/event/handler/EventHandlerContext.java b/src/org/apollo/game/event/handler/EventHandlerContext.java new file mode 100644 index 000000000..12597531b --- /dev/null +++ b/src/org/apollo/game/event/handler/EventHandlerContext.java @@ -0,0 +1,17 @@ +package org.apollo.game.event.handler; + +import org.apollo.game.event.handler.chain.EventHandlerChain; + +/** + * Provides operations specific to an {@link EventHandler} in an + * {@link EventHandlerChain}. + * @author Graham + */ +public abstract class EventHandlerContext { + + /** + * Breaks the handler chain. + */ + public abstract void breakHandlerChain(); + +} diff --git a/src/org/apollo/game/event/handler/chain/EventHandlerChain.java b/src/org/apollo/game/event/handler/chain/EventHandlerChain.java new file mode 100644 index 000000000..da0850982 --- /dev/null +++ b/src/org/apollo/game/event/handler/chain/EventHandlerChain.java @@ -0,0 +1,67 @@ +package org.apollo.game.event.handler.chain; + +import org.apollo.game.event.Event; +import org.apollo.game.event.handler.EventHandler; +import org.apollo.game.event.handler.EventHandlerContext; +import org.apollo.game.model.Player; + +/** + * A chain of event handlers. + * @author Graham + * @param The type of event the handlers in this chain handle. + */ +public final class EventHandlerChain { + + /** + * The handlers. + */ + private EventHandler[] handlers; + + /** + * Creates the event handler chain. + * @param handlers The handlers. + */ + public EventHandlerChain(EventHandler... handlers) { + this.handlers = handlers; + } + + /** + * Dynamically adds an event handler to the end of the chain. + * @param handler The handler. + */ + @SuppressWarnings("unchecked") + public void addLast(EventHandler handler) { + EventHandler[] old = handlers; + handlers = new EventHandler[old.length + 1]; + System.arraycopy(old, 0, handlers, 0, old.length); + handlers[old.length] = handler; + } + + /** + * Handles the event, passing it down the chain until the chain is broken + * or the event reaches the end of the chain. + * @param player The player. + * @param event The event. + */ + public void handle(Player player, E event) { + final boolean[] running = new boolean[1]; + running[0] = true; + + EventHandlerContext ctx = new EventHandlerContext() { + + @Override + public void breakHandlerChain() { + running[0] = false; + } + + }; + + for (EventHandler handler : handlers) { + handler.handle(ctx, player, event); + if (!running[0]) { + break; + } + } + } + +} diff --git a/src/org/apollo/game/event/handler/chain/EventHandlerChainGroup.java b/src/org/apollo/game/event/handler/chain/EventHandlerChainGroup.java new file mode 100644 index 000000000..294221fd3 --- /dev/null +++ b/src/org/apollo/game/event/handler/chain/EventHandlerChainGroup.java @@ -0,0 +1,38 @@ +package org.apollo.game.event.handler.chain; + +import java.util.Map; + +import org.apollo.game.event.Event; + +/** + * A group of {@link EventHandlerChain}s classified by the {@link Event} type. + * @author Graham + */ +public final class EventHandlerChainGroup { + + /** + * The map of event classes to event handler chains. + */ + private final Map, EventHandlerChain> chains; + + /** + * Creates the event handler chain group. + * @param chains The chains map. + */ + public EventHandlerChainGroup(Map, EventHandlerChain> chains) { + this.chains = chains; + } + + /** + * Gets an {@link EventHandlerChain} from this group. + * @param The type of event. + * @param clazz The event class. + * @return The {@link EventHandlerChain} if one was found, {@code null} + * otherwise. + */ + @SuppressWarnings("unchecked") + public EventHandlerChain getChain(Class clazz) { + return (EventHandlerChain) chains.get(clazz); + } + +} diff --git a/src/org/apollo/game/event/handler/chain/package-info.java b/src/org/apollo/game/event/handler/chain/package-info.java new file mode 100644 index 000000000..4f2c1ead1 --- /dev/null +++ b/src/org/apollo/game/event/handler/chain/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains classes related to the chaining of event handlers. + */ +package org.apollo.game.event.handler.chain; diff --git a/src/org/apollo/game/event/handler/impl/BankButtonEventHandler.java b/src/org/apollo/game/event/handler/impl/BankButtonEventHandler.java new file mode 100644 index 000000000..fbd363162 --- /dev/null +++ b/src/org/apollo/game/event/handler/impl/BankButtonEventHandler.java @@ -0,0 +1,34 @@ +package org.apollo.game.event.handler.impl; + +import org.apollo.game.event.handler.EventHandler; +import org.apollo.game.event.handler.EventHandlerContext; +import org.apollo.game.event.impl.ButtonEvent; +import org.apollo.game.model.Player; + +/** + * An {@link EventHandler} which responds to {@link ButtonEvent}s for + * withdrawing items as notes. + * @author Graham + */ +public final class BankButtonEventHandler extends EventHandler { + + /** + * The withdraw as item button id. + */ + private static final int WITHDRAW_AS_ITEM = 5387; + + /** + * The withdraw as note button id. + */ + private static final int WITHDRAW_AS_NOTE = 5386; + + @Override + public void handle(EventHandlerContext ctx, Player player, ButtonEvent event) { + if (event.getInterfaceId() == WITHDRAW_AS_ITEM) { + player.setWithdrawingNotes(false); + } else if (event.getInterfaceId() == WITHDRAW_AS_NOTE) { + player.setWithdrawingNotes(true); + } + } + +} diff --git a/src/org/apollo/game/event/handler/impl/BankEventHandler.java b/src/org/apollo/game/event/handler/impl/BankEventHandler.java new file mode 100644 index 000000000..219c015d8 --- /dev/null +++ b/src/org/apollo/game/event/handler/impl/BankEventHandler.java @@ -0,0 +1,88 @@ +package org.apollo.game.event.handler.impl; + +import org.apollo.game.event.handler.EventHandler; +import org.apollo.game.event.handler.EventHandlerContext; +import org.apollo.game.event.impl.ItemActionEvent; +import org.apollo.game.model.Player; +import org.apollo.game.model.inter.bank.BankConstants; +import org.apollo.game.model.inter.bank.BankDepositEnterAmountListener; +import org.apollo.game.model.inter.bank.BankUtils; +import org.apollo.game.model.inter.bank.BankWithdrawEnterAmountListener; + +/** + * An event handler which handles withdrawing and depositing items from/to a + * player's bank. + * @author Graham + */ +public final class BankEventHandler extends EventHandler { + + /** + * Converts an option to an amount. + * @param option The option. + * @return The amount. + * @throws IllegalArgumentException if the option is not legal. + */ + private static final int optionToAmount(int option) { + switch (option) { + case 1: + return 1; + case 2: + return 5; + case 3: + return 10; + case 4: + return Integer.MAX_VALUE; + case 5: + return -1; + } + throw new IllegalArgumentException(); + } + + @Override + public void handle(EventHandlerContext ctx, Player player, ItemActionEvent event) { + if (!player.getInterfaceSet().contains(BankConstants.BANK_WINDOW_ID)) { + return; + } + + if (event.getInterfaceId() == BankConstants.SIDEBAR_INVENTORY_ID) { + deposit(ctx, player, event); + } else if (event.getInterfaceId() == BankConstants.BANK_INVENTORY_ID) { + withdraw(ctx, player, event); + } + } + + /** + * Handles a withdraw action. + * @param ctx The event handler context. + * @param player The player. + * @param event The event. + */ + private void withdraw(EventHandlerContext ctx, Player player, ItemActionEvent event) { + int amount = optionToAmount(event.getOption()); + if (amount == -1) { + player.getInterfaceSet().openEnterAmountDialog(new BankWithdrawEnterAmountListener(player, event.getSlot(), event.getId())); + } else { + if (!BankUtils.withdraw(player, event.getSlot(), event.getId(), amount)) { + ctx.breakHandlerChain(); + } + } + } + + /** + * Handles a deposit action. + * @param ctx The event handler context. + * @param player The player. + * @param event The event. + */ + private void deposit(EventHandlerContext ctx, Player player, ItemActionEvent event) { + int amount = optionToAmount(event.getOption()); + if (amount == -1) { + player.getInterfaceSet().openEnterAmountDialog(new BankDepositEnterAmountListener(player, event.getSlot(), event.getId())); + } else { + if (!BankUtils.deposit(player, event.getSlot(), event.getId(), amount)) { + ctx.breakHandlerChain(); + } + } + } + +} diff --git a/src/org/apollo/game/event/handler/impl/CharacterDesignEventHandler.java b/src/org/apollo/game/event/handler/impl/CharacterDesignEventHandler.java new file mode 100644 index 000000000..ba796681e --- /dev/null +++ b/src/org/apollo/game/event/handler/impl/CharacterDesignEventHandler.java @@ -0,0 +1,22 @@ +package org.apollo.game.event.handler.impl; + +import org.apollo.game.event.handler.EventHandler; +import org.apollo.game.event.handler.EventHandlerContext; +import org.apollo.game.event.impl.CharacterDesignEvent; +import org.apollo.game.event.impl.CloseInterfaceEvent; +import org.apollo.game.model.Player; + +/** + * A handler which handles {@link CharacterDesignEvent}s. + * @author Graham + */ +public final class CharacterDesignEventHandler extends EventHandler { + + @Override + public void handle(EventHandlerContext ctx, Player player, CharacterDesignEvent event) { + player.setAppearance(event.getAppearance()); + player.setDesignedCharacter(true); + player.send(new CloseInterfaceEvent()); + } + +} diff --git a/src/org/apollo/game/event/handler/impl/CharacterDesignVerificationHandler.java b/src/org/apollo/game/event/handler/impl/CharacterDesignVerificationHandler.java new file mode 100644 index 000000000..af790242d --- /dev/null +++ b/src/org/apollo/game/event/handler/impl/CharacterDesignVerificationHandler.java @@ -0,0 +1,81 @@ +package org.apollo.game.event.handler.impl; + +import org.apollo.game.event.handler.EventHandler; +import org.apollo.game.event.handler.EventHandlerContext; +import org.apollo.game.event.impl.CharacterDesignEvent; +import org.apollo.game.model.Appearance; +import org.apollo.game.model.Gender; +import org.apollo.game.model.Player; + +/** + * A handler which verifies {@link CharacterDesignEvent}s. + * @author Graham + */ +public final class CharacterDesignVerificationHandler extends EventHandler { + + @Override + public void handle(EventHandlerContext ctx, Player player, CharacterDesignEvent event) { + if (!valid(event.getAppearance()) || player.hasDesignedCharacter()) { + ctx.breakHandlerChain(); + } + } + + /** + * Checks if an appearance combination is valid. + * @param appearance The appearance combination. + * @return {@code true} if so, {@code false} if not. + */ + private boolean valid(Appearance appearance) { + int[] colors = appearance.getColors(); + int[] maxColors = new int[] { 11, 15, 15, 5, 7 }; + for (int i = 0; i < colors.length; i++) { + if (colors[i] < 0 || colors[i] > maxColors[i]) { + return false; + } + } + + Gender gender = appearance.getGender(); + if (gender == Gender.MALE) { + return validMaleStyle(appearance); + } else if (gender == Gender.FEMALE) { + return validFemaleStyle(appearance); + } else { + return false; // maybe null? + } + } + + /** + * Checks if a {@link Gender#MALE} style combination is valid. + * @param appearance The appearance combination. + * @return {@code true} if so, {@code false} if not. + */ + private boolean validMaleStyle(Appearance appearance) { + int[] styles = appearance.getStyle(); + int[] minStyles = new int[] { 0, 10, 18, 26, 33, 36, 42 }; + int[] maxStyles = new int[] { 8, 17, 25, 31, 34, 40, 43 }; + for (int i = 0; i < styles.length; i++) { + if (styles[i] < minStyles[i] || styles[i] > maxStyles[i]) { + return false; + } + } + return true; + } + + /** + * Checks if a {@link Gender#FEMALE} style combination is valid. + * @param appearance The appearance combination. + * @return {@code true} if so, {@code false} if not. + */ + private boolean validFemaleStyle(Appearance appearance) { + int[] styles = appearance.getStyle(); + int[] minStyles = new int[] { 45, 255, 56, 61, 67, 70, 79 }; + int[] maxStyles = new int[] { 54, 255, 60, 65, 68, 77, 80 }; + for (int i = 0; i < styles.length; i++) { + if (styles[i] < minStyles[i] || styles[i] > maxStyles[i]) { + return false; + } + } + return true; + } + +} diff --git a/src/org/apollo/game/event/handler/impl/ChatEventHandler.java b/src/org/apollo/game/event/handler/impl/ChatEventHandler.java new file mode 100644 index 000000000..410f422de --- /dev/null +++ b/src/org/apollo/game/event/handler/impl/ChatEventHandler.java @@ -0,0 +1,20 @@ +package org.apollo.game.event.handler.impl; + +import org.apollo.game.event.handler.EventHandler; +import org.apollo.game.event.handler.EventHandlerContext; +import org.apollo.game.event.impl.ChatEvent; +import org.apollo.game.model.Player; +import org.apollo.game.sync.block.SynchronizationBlock; + +/** + * An event handler which broadcasts public chat messages. + * @author Graham + */ +public final class ChatEventHandler extends EventHandler { + + @Override + public void handle(EventHandlerContext ctx, Player player, ChatEvent event) { + player.getBlockSet().add(SynchronizationBlock.createChatBlock(player, event)); + } + +} diff --git a/src/org/apollo/game/event/handler/impl/ChatVerificationHandler.java b/src/org/apollo/game/event/handler/impl/ChatVerificationHandler.java new file mode 100644 index 000000000..6a041c9c5 --- /dev/null +++ b/src/org/apollo/game/event/handler/impl/ChatVerificationHandler.java @@ -0,0 +1,23 @@ +package org.apollo.game.event.handler.impl; + +import org.apollo.game.event.handler.EventHandler; +import org.apollo.game.event.handler.EventHandlerContext; +import org.apollo.game.event.impl.ChatEvent; +import org.apollo.game.model.Player; + +/** + * An event handler which verifies chat events. + * @author Graham + */ +public final class ChatVerificationHandler extends EventHandler { + + @Override + public void handle(EventHandlerContext ctx, Player player, ChatEvent event) { + int color = event.getTextColor(); + int effects = event.getTextEffects(); + if (color < 0 || color > 11 || effects < 0 || effects > 5) { + ctx.breakHandlerChain(); + } + } + +} diff --git a/src/org/apollo/game/event/handler/impl/ClosedInterfaceEventHandler.java b/src/org/apollo/game/event/handler/impl/ClosedInterfaceEventHandler.java new file mode 100644 index 000000000..0eeca8245 --- /dev/null +++ b/src/org/apollo/game/event/handler/impl/ClosedInterfaceEventHandler.java @@ -0,0 +1,19 @@ +package org.apollo.game.event.handler.impl; + +import org.apollo.game.event.handler.EventHandler; +import org.apollo.game.event.handler.EventHandlerContext; +import org.apollo.game.event.impl.ClosedInterfaceEvent; +import org.apollo.game.model.Player; + +/** + * An {@link EventHandler} for the {@link ClosedInterfaceEvent}. + * @author Graham + */ +public final class ClosedInterfaceEventHandler extends EventHandler { + + @Override + public void handle(EventHandlerContext ctx, Player player, ClosedInterfaceEvent event) { + player.getInterfaceSet().interfaceClosed(); + } + +} diff --git a/src/org/apollo/game/event/handler/impl/CommandEventHandler.java b/src/org/apollo/game/event/handler/impl/CommandEventHandler.java new file mode 100644 index 000000000..93fad89f7 --- /dev/null +++ b/src/org/apollo/game/event/handler/impl/CommandEventHandler.java @@ -0,0 +1,31 @@ +package org.apollo.game.event.handler.impl; + +import org.apollo.game.command.Command; +import org.apollo.game.event.handler.EventHandler; +import org.apollo.game.event.handler.EventHandlerContext; +import org.apollo.game.event.impl.CommandEvent; +import org.apollo.game.model.Player; +import org.apollo.game.model.World; + +/** + * An {@link EventHandler} which dispatches {@link CommandEvent}s. + * @author Graham + */ +public final class CommandEventHandler extends EventHandler { + + @Override + public void handle(EventHandlerContext ctx, Player player, CommandEvent event) { + String str = event.getCommand(); + String[] components = str.split(" "); + + String name = components[0]; + String[] arguments = new String[components.length - 1]; + + System.arraycopy(components, 1, arguments, 0, arguments.length); + + Command command = new Command(name, arguments); + + World.getWorld().getCommandDispatcher().dispatch(player, command); + } + +} diff --git a/src/org/apollo/game/event/handler/impl/EnteredAmountEventHandler.java b/src/org/apollo/game/event/handler/impl/EnteredAmountEventHandler.java new file mode 100644 index 000000000..9a3ca8cc8 --- /dev/null +++ b/src/org/apollo/game/event/handler/impl/EnteredAmountEventHandler.java @@ -0,0 +1,19 @@ +package org.apollo.game.event.handler.impl; + +import org.apollo.game.event.handler.EventHandler; +import org.apollo.game.event.handler.EventHandlerContext; +import org.apollo.game.event.impl.EnteredAmountEvent; +import org.apollo.game.model.Player; + +/** + * An {@link EventHandler} for the {@link EnteredAmountEvent}. + * @author Graham + */ +public final class EnteredAmountEventHandler extends EventHandler { + + @Override + public void handle(EventHandlerContext ctx, Player player, EnteredAmountEvent event) { + player.getInterfaceSet().enteredAmount(event.getAmount()); + } + +} diff --git a/src/org/apollo/game/event/handler/impl/EquipEventHandler.java b/src/org/apollo/game/event/handler/impl/EquipEventHandler.java new file mode 100644 index 000000000..42915dca0 --- /dev/null +++ b/src/org/apollo/game/event/handler/impl/EquipEventHandler.java @@ -0,0 +1,143 @@ +package org.apollo.game.event.handler.impl; + +import org.apollo.game.event.handler.EventHandler; +import org.apollo.game.event.handler.EventHandlerContext; +import org.apollo.game.event.impl.EquipEvent; +import org.apollo.game.model.EquipmentConstants; +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; + +/** + * An event handler which equips items. + * @author Graham + */ +public final class EquipEventHandler extends EventHandler { + + @Override + public void handle(EventHandlerContext ctx, Player player, EquipEvent event) { + if (event.getInterfaceId() == SynchronizationInventoryListener.INVENTORY_ID) { + int slot = event.getSlot(); + if (slot < 0 || slot >= player.getInventory().capacity()) { + ctx.breakHandlerChain(); + return; + } + + Item item = player.getInventory().get(slot); + if (item == null || item.getId() != event.getId()) { + ctx.breakHandlerChain(); + return; + } + + ItemDefinition itemDef = item.getDefinition(); + EquipmentDefinition equipDef = EquipmentDefinition.forId(item.getId()); + if (equipDef == null) { + ctx.breakHandlerChain(); + return; + } + + SkillSet skillSet = player.getSkillSet(); + if (skillSet.getSkill(Skill.ATTACK).getMaximumLevel() < equipDef.getAttackLevel()) { + player.sendMessage("You need an Attack level of " + equipDef.getAttackLevel() + " to equip this item."); + ctx.breakHandlerChain(); + return; + } + if (skillSet.getSkill(Skill.STRENGTH).getMaximumLevel() < equipDef.getStrengthLevel()) { + player.sendMessage("You need a Strength level of " + equipDef.getStrengthLevel() + " to equip this item."); + ctx.breakHandlerChain(); + return; + } + if (skillSet.getSkill(Skill.DEFENCE).getMaximumLevel() < equipDef.getDefenceLevel()) { + player.sendMessage("You need a Defence level of " + equipDef.getDefenceLevel() + " to equip this item."); + ctx.breakHandlerChain(); + return; + } + if (skillSet.getSkill(Skill.RANGED).getMaximumLevel() < equipDef.getRangedLevel()) { + player.sendMessage("You need a Ranged level of " + equipDef.getRangedLevel() + " to equip this item."); + ctx.breakHandlerChain(); + return; + } + if (skillSet.getSkill(Skill.MAGIC).getMaximumLevel() < equipDef.getMagicLevel()) { + player.sendMessage("You need a Magic level of " + equipDef.getMagicLevel() + " to equip this item."); + ctx.breakHandlerChain(); + return; + } + + Inventory inventory = player.getInventory(); + Inventory equipment = player.getEquipment(); + + int equipmentSlot = equipDef.getSlot(); + + // TODO: equip event decoder for 317, and remove event decoder for both + // TODO: put all this into another method somewhere + + // check if there is enough space for a two handed weapon + if (equipDef.isTwoHanded()) { + Item currentShield = equipment.get(EquipmentConstants.SHIELD); + if (currentShield != null) { + if (inventory.freeSlots() < 1) { + inventory.forceCapacityExceeded(); + ctx.breakHandlerChain(); + return; + } + } + } + + // check if a shield is being added with a two handed weapon + boolean removeWeapon = false; + if (equipmentSlot == EquipmentConstants.SHIELD) { + Item currentWeapon = equipment.get(EquipmentConstants.WEAPON); + if (currentWeapon != null) { + EquipmentDefinition weaponDef = EquipmentDefinition.forId(currentWeapon.getId()); + if (weaponDef.isTwoHanded()) { + if (inventory.freeSlots() < 1) { + inventory.forceCapacityExceeded(); + ctx.breakHandlerChain(); + return; + } + removeWeapon = true; + } + } + } + + Item previous = equipment.get(equipmentSlot); + if (itemDef.isStackable() && previous != null && previous.getId() == item.getId()) { + // we know the item is there, so we can let the inventory class do its stacking magic + inventory.remove(item); + Item tmp = equipment.add(item); + if (tmp != null) { + inventory.add(tmp); + } + } else { + // swap the weapons around + Item tmp = equipment.reset(equipmentSlot); + equipment.set(equipmentSlot, item); + inventory.reset(slot); + if (tmp != null) { + inventory.add(tmp); + } + } + + // remove the shield if this weapon is two handed + if (equipDef.isTwoHanded()) { + Item tmp = equipment.reset(EquipmentConstants.SHIELD); + // we know tmp will not be null from the check above + inventory.add(tmp); + } + + if (removeWeapon) { + Item tmp = equipment.reset(EquipmentConstants.WEAPON); + // we know tmp will not be null from the check about + inventory.add(tmp); + } + + ctx.breakHandlerChain(); + } + } + +} diff --git a/src/org/apollo/game/event/handler/impl/RemoveEventHandler.java b/src/org/apollo/game/event/handler/impl/RemoveEventHandler.java new file mode 100644 index 000000000..5cd1c1686 --- /dev/null +++ b/src/org/apollo/game/event/handler/impl/RemoveEventHandler.java @@ -0,0 +1,61 @@ +package org.apollo.game.event.handler.impl; + +import org.apollo.game.event.handler.EventHandler; +import org.apollo.game.event.handler.EventHandlerContext; +import org.apollo.game.event.impl.ItemActionEvent; +import org.apollo.game.model.Inventory; +import org.apollo.game.model.Item; +import org.apollo.game.model.Player; +import org.apollo.game.model.inv.SynchronizationInventoryListener; + +/** + * An event handler which removes equipped items. + * @author Graham + */ +public final class RemoveEventHandler extends EventHandler { + + @Override + public void handle(EventHandlerContext ctx, Player player, ItemActionEvent event) { + if (event.getOption() == 1 && event.getInterfaceId() == SynchronizationInventoryListener.EQUIPMENT_ID) { + Inventory inventory = player.getInventory(); + Inventory equipment = player.getEquipment(); + + int slot = event.getSlot(); + if (slot < 0 || slot >= equipment.capacity()) { + ctx.breakHandlerChain(); + return; + } + + Item item = equipment.get(slot); + if (item == null || item.getId() != event.getId()) { + ctx.breakHandlerChain(); + return; + } + + boolean removed = true; + + inventory.stopFiringEvents(); + equipment.stopFiringEvents(); + + try { + equipment.set(slot, null); + Item tmp = inventory.add(item); + if (tmp != null) { + removed = false; + equipment.set(slot, tmp); + } + } finally { + inventory.startFiringEvents(); + equipment.startFiringEvents(); + } + + if (removed) { + inventory.forceRefresh(); // TODO find out the specific slot that got used? + equipment.forceRefresh(slot); + } else { + inventory.forceCapacityExceeded(); + } + } + } + +} diff --git a/src/org/apollo/game/event/handler/impl/SwitchItemEventHandler.java b/src/org/apollo/game/event/handler/impl/SwitchItemEventHandler.java new file mode 100644 index 000000000..2bc5e8201 --- /dev/null +++ b/src/org/apollo/game/event/handler/impl/SwitchItemEventHandler.java @@ -0,0 +1,46 @@ +package org.apollo.game.event.handler.impl; + +import org.apollo.game.event.handler.EventHandler; +import org.apollo.game.event.handler.EventHandlerContext; +import org.apollo.game.event.impl.SwitchItemEvent; +import org.apollo.game.model.Inventory; +import org.apollo.game.model.Player; +import org.apollo.game.model.inter.bank.BankConstants; +import org.apollo.game.model.inv.SynchronizationInventoryListener; + +/** + * An {@link EventHandler} which updates an {@link Inventory} when the client + * sends a {@link SwitchItemEvent} to the server. + * @author Graham + */ +public final class SwitchItemEventHandler extends EventHandler { + + @Override + public void handle(EventHandlerContext ctx, Player player, SwitchItemEvent event) { + Inventory inventory; + boolean insertPermitted = false; + + // TODO is there a better way of doing this?? + switch (event.getInterfaceId()) { + case SynchronizationInventoryListener.INVENTORY_ID: + case BankConstants.SIDEBAR_INVENTORY_ID: + inventory = player.getInventory(); + break; + case SynchronizationInventoryListener.EQUIPMENT_ID: + inventory = player.getEquipment(); + break; + case BankConstants.BANK_INVENTORY_ID: + inventory = player.getBank(); + insertPermitted = true; + break; + default: + return; // not a known inventory, ignore + } + + if (event.getOldSlot() >= 0 && event.getNewSlot() >= 0 && event.getOldSlot() < inventory.capacity() && event.getNewSlot() < inventory.capacity()) { + // events must be fired for it to work if a sidebar inv overlay is used + inventory.swap(insertPermitted ? event.isInserting() : false, event.getOldSlot(), event.getNewSlot()); + } + } + +} diff --git a/src/org/apollo/game/event/handler/impl/WalkEventHandler.java b/src/org/apollo/game/event/handler/impl/WalkEventHandler.java new file mode 100644 index 000000000..a688a1ccd --- /dev/null +++ b/src/org/apollo/game/event/handler/impl/WalkEventHandler.java @@ -0,0 +1,40 @@ +package org.apollo.game.event.handler.impl; + +import org.apollo.game.event.handler.EventHandler; +import org.apollo.game.event.handler.EventHandlerContext; +import org.apollo.game.event.impl.WalkEvent; +import org.apollo.game.model.Player; +import org.apollo.game.model.Position; +import org.apollo.game.model.WalkingQueue; + +/** + * A handler for the {@link WalkEvent}. + * @author Graham + */ +public final class WalkEventHandler extends EventHandler { + + @Override + public void handle(EventHandlerContext ctx, Player player, WalkEvent event) { + WalkingQueue queue = player.getWalkingQueue(); + + Position[] steps = event.getSteps(); + for (int i = 0; i < steps.length; i++) { + Position step = steps[i]; + if (i == 0) { + if (!queue.addFirstStep(step)) { + return; /* ignore packet */ + } + } else { + queue.addStep(step); + } + } + + queue.setRunningQueue(event.isRunning()); + + if (queue.size() > 0) { + player.stopAction(); + player.getInterfaceSet().close(); // TODO: should this be done if size == 0? + } + } + +} diff --git a/src/org/apollo/game/event/handler/impl/package-info.java b/src/org/apollo/game/event/handler/impl/package-info.java new file mode 100644 index 000000000..97bc82ef0 --- /dev/null +++ b/src/org/apollo/game/event/handler/impl/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains event handler implementations. + */ +package org.apollo.game.event.handler.impl; diff --git a/src/org/apollo/game/event/handler/package-info.java b/src/org/apollo/game/event/handler/package-info.java new file mode 100644 index 000000000..99c6999b9 --- /dev/null +++ b/src/org/apollo/game/event/handler/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains classes which define abstract event handlers. + */ +package org.apollo.game.event.handler; diff --git a/src/org/apollo/game/event/impl/ButtonEvent.java b/src/org/apollo/game/event/impl/ButtonEvent.java new file mode 100644 index 000000000..f33f46b1b --- /dev/null +++ b/src/org/apollo/game/event/impl/ButtonEvent.java @@ -0,0 +1,32 @@ +package org.apollo.game.event.impl; + +import org.apollo.game.event.Event; + +/** + * An event sent when the client clicks a button. + * @author Graham + */ +public final class ButtonEvent extends Event { + + /** + * The interface id. + */ + private final int interfaceId; + + /** + * Creates the button event. + * @param interfaceId The interface id. + */ + public ButtonEvent(int interfaceId) { + this.interfaceId = interfaceId; + } + + /** + * Gets the interface id. + * @return The interface id. + */ + public int getInterfaceId() { + return interfaceId; + } + +} diff --git a/src/org/apollo/game/event/impl/CharacterDesignEvent.java b/src/org/apollo/game/event/impl/CharacterDesignEvent.java new file mode 100644 index 000000000..bb3f5fa82 --- /dev/null +++ b/src/org/apollo/game/event/impl/CharacterDesignEvent.java @@ -0,0 +1,34 @@ +package org.apollo.game.event.impl; + +import org.apollo.game.event.Event; +import org.apollo.game.model.Appearance; + +/** + * An event sent by the client when the player modifies their character's + * design. + * @author Graham + */ +public final class CharacterDesignEvent extends Event { + + /** + * The appearance. + */ + private final Appearance appearance; + + /** + * Creates the character design event. + * @param appearance The appearance. + */ + public CharacterDesignEvent(Appearance appearance) { + this.appearance = appearance; + } + + /** + * Gets the appearance. + * @return The appearance. + */ + public Appearance getAppearance() { + return appearance; + } + +} diff --git a/src/org/apollo/game/event/impl/ChatEvent.java b/src/org/apollo/game/event/impl/ChatEvent.java new file mode 100644 index 000000000..e85275b0f --- /dev/null +++ b/src/org/apollo/game/event/impl/ChatEvent.java @@ -0,0 +1,77 @@ +package org.apollo.game.event.impl; + +import org.apollo.game.event.Event; + +/** + * An event sent by the client to send a public chat message to other players. + * @author Graham + */ +public final class ChatEvent extends Event { + + /** + * The message. + */ + private final String message; + + /** + * The compressed message. + */ + private final byte[] compressedMessage; + + /** + * The text color. + */ + private final int color; + + /** + * The text effects. + */ + private final int effects; + + /** + * Creates a new chat event. + * @param message The message. + * @param compressedMessage The compressed message. + * @param color The text color. + * @param effects The text effects. + */ + public ChatEvent(String message, byte[] compressedMessage, int color, int effects) { + this.message = message; + this.compressedMessage = compressedMessage; + this.color = color; + this.effects = effects; + } + + /** + * Gets the message. + * @return The message. + */ + public String getMessage() { + return message; + } + + /** + * Gets the text color. + * @return The text color. + */ + public int getTextColor() { + return color; + } + + /** + * Gets the text effects. + * @return The text effects. + */ + public int getTextEffects() { + return effects; + } + + /** + * Gets the compressed message. + * @return The compressed message. + */ + public byte[] getCompressedMessage() { + return compressedMessage; + } + +} diff --git a/src/org/apollo/game/event/impl/CloseInterfaceEvent.java b/src/org/apollo/game/event/impl/CloseInterfaceEvent.java new file mode 100644 index 000000000..1e4f8758e --- /dev/null +++ b/src/org/apollo/game/event/impl/CloseInterfaceEvent.java @@ -0,0 +1,11 @@ +package org.apollo.game.event.impl; + +import org.apollo.game.event.Event; + +/** + * An event which closes the open interface. + * @author Graham + */ +public final class CloseInterfaceEvent extends Event { + +} diff --git a/src/org/apollo/game/event/impl/ClosedInterfaceEvent.java b/src/org/apollo/game/event/impl/ClosedInterfaceEvent.java new file mode 100644 index 000000000..48165e26c --- /dev/null +++ b/src/org/apollo/game/event/impl/ClosedInterfaceEvent.java @@ -0,0 +1,11 @@ +package org.apollo.game.event.impl; + +import org.apollo.game.event.Event; + +/** + * Sent by the client when the current interface is closed. + * @author Graham + */ +public final class ClosedInterfaceEvent extends Event { + +} diff --git a/src/org/apollo/game/event/impl/CommandEvent.java b/src/org/apollo/game/event/impl/CommandEvent.java new file mode 100644 index 000000000..3842e8efd --- /dev/null +++ b/src/org/apollo/game/event/impl/CommandEvent.java @@ -0,0 +1,32 @@ +package org.apollo.game.event.impl; + +import org.apollo.game.event.Event; + +/** + * An event issued by the client to send a {@code ::} command/ + * @author Graham + */ +public final class CommandEvent extends Event { + + /** + * The command. + */ + private final String command; + + /** + * Creates the command event. + * @param command The command. + */ + public CommandEvent(String command) { + this.command = command; + } + + /** + * Gets the command. + * @return The command. + */ + public String getCommand() { + return command; + } + +} diff --git a/src/org/apollo/game/event/impl/EnterAmountEvent.java b/src/org/apollo/game/event/impl/EnterAmountEvent.java new file mode 100644 index 000000000..3d0fe6757 --- /dev/null +++ b/src/org/apollo/game/event/impl/EnterAmountEvent.java @@ -0,0 +1,12 @@ +package org.apollo.game.event.impl; + +import org.apollo.game.event.Event; + +/** + * An {@link Event} which is sent to the client to open up the enter amount + * interface. + * @author Graham + */ +public final class EnterAmountEvent extends Event { + +} diff --git a/src/org/apollo/game/event/impl/EnteredAmountEvent.java b/src/org/apollo/game/event/impl/EnteredAmountEvent.java new file mode 100644 index 000000000..ced896753 --- /dev/null +++ b/src/org/apollo/game/event/impl/EnteredAmountEvent.java @@ -0,0 +1,32 @@ +package org.apollo.game.event.impl; + +import org.apollo.game.event.Event; + +/** + * An event sent by the client when the player has entered an amount. + * @author Graham + */ +public final class EnteredAmountEvent extends Event { + + /** + * The amount. + */ + private final int amount; + + /** + * Creates the entered amount event. + * @param amount The amount. + */ + public EnteredAmountEvent(int amount) { + this.amount = amount; + } + + /** + * Gets the amount. + * @return The amount. + */ + public int getAmount() { + return amount; + } + +} diff --git a/src/org/apollo/game/event/impl/EquipEvent.java b/src/org/apollo/game/event/impl/EquipEvent.java new file mode 100644 index 000000000..f1b2c661d --- /dev/null +++ b/src/org/apollo/game/event/impl/EquipEvent.java @@ -0,0 +1,62 @@ +package org.apollo.game.event.impl; + +import org.apollo.game.event.Event; + +/** + * An event sent by the client to request that an item is equipped. + * @author Graham + */ +public final class EquipEvent extends Event { + + /** + * The interface id. + */ + private final int interfaceId; + + /** + * The item id. + */ + private final int id; + + /** + * The item's slot. + */ + private final int slot; + + /** + * Creates the equip event. + * @param interfaceId The interface id. + * @param id The id. + * @param slot The slot. + */ + public EquipEvent(int interfaceId, int id, int slot) { + this.interfaceId = interfaceId; + this.id = id; + this.slot = slot; + } + + /** + * Gets the interface id. + * @return The interface id. + */ + public int getInterfaceId() { + return interfaceId; + } + + /** + * Gets the item id. + * @return The item id. + */ + public int getId() { + return id; + } + + /** + * Gets the slot. + * @return The slot. + */ + public int getSlot() { + return slot; + } + +} diff --git a/src/org/apollo/game/event/impl/FifthItemActionEvent.java b/src/org/apollo/game/event/impl/FifthItemActionEvent.java new file mode 100644 index 000000000..07104f2db --- /dev/null +++ b/src/org/apollo/game/event/impl/FifthItemActionEvent.java @@ -0,0 +1,19 @@ +package org.apollo.game.event.impl; + +/** + * The fifth {@link ItemActionEvent}. + * @author Graham + */ +public final class FifthItemActionEvent extends ItemActionEvent { + + /** + * Creates the fifth item action event. + * @param interfaceId The interface id. + * @param id The item id. + * @param slot The item slot. + */ + public FifthItemActionEvent(int interfaceId, int id, int slot) { + super(5, interfaceId, id, slot); + } + +} diff --git a/src/org/apollo/game/event/impl/FirstItemActionEvent.java b/src/org/apollo/game/event/impl/FirstItemActionEvent.java new file mode 100644 index 000000000..179f87ba0 --- /dev/null +++ b/src/org/apollo/game/event/impl/FirstItemActionEvent.java @@ -0,0 +1,19 @@ +package org.apollo.game.event.impl; + +/** + * The first {@link ItemActionEvent}. + * @author Graham + */ +public final class FirstItemActionEvent extends ItemActionEvent { + + /** + * Creates the first item action event. + * @param interfaceId The interface id. + * @param id The item id. + * @param slot The item slot. + */ + public FirstItemActionEvent(int interfaceId, int id, int slot) { + super(1, interfaceId, id, slot); + } + +} diff --git a/src/org/apollo/game/event/impl/FirstObjectActionEvent.java b/src/org/apollo/game/event/impl/FirstObjectActionEvent.java new file mode 100644 index 000000000..9d2973ced --- /dev/null +++ b/src/org/apollo/game/event/impl/FirstObjectActionEvent.java @@ -0,0 +1,20 @@ +package org.apollo.game.event.impl; + +import org.apollo.game.model.Position; + +/** + * An event sent when the first option at an object is used. + * @author Graham + */ +public final class FirstObjectActionEvent extends ObjectActionEvent { + + /** + * Creates the first object action event. + * @param id The id. + * @param position The position. + */ + public FirstObjectActionEvent(int id, Position position) { + super(1, id, position); + } + +} diff --git a/src/org/apollo/game/event/impl/FourthItemActionEvent.java b/src/org/apollo/game/event/impl/FourthItemActionEvent.java new file mode 100644 index 000000000..184041181 --- /dev/null +++ b/src/org/apollo/game/event/impl/FourthItemActionEvent.java @@ -0,0 +1,19 @@ +package org.apollo.game.event.impl; + +/** + * The fourth {@link ItemActionEvent}. + * @author Graham + */ +public final class FourthItemActionEvent extends ItemActionEvent { + + /** + * Creates the fourth item action event. + * @param interfaceId The interface id. + * @param id The item id. + * @param slot The item slot. + */ + public FourthItemActionEvent(int interfaceId, int id, int slot) { + super(4, interfaceId, id, slot); + } + +} diff --git a/src/org/apollo/game/event/impl/IdAssignmentEvent.java b/src/org/apollo/game/event/impl/IdAssignmentEvent.java new file mode 100644 index 000000000..c4d777015 --- /dev/null +++ b/src/org/apollo/game/event/impl/IdAssignmentEvent.java @@ -0,0 +1,48 @@ +package org.apollo.game.event.impl; + +import org.apollo.game.event.Event; + +/** + * An event which specifies the local id and membership status of the current + * player. + * @author Graham + */ +public final class IdAssignmentEvent extends Event { + + /** + * The id of this player. + */ + private final int id; + + /** + * The membership flag. + */ + private final boolean members; + + /** + * Creates the local id event. + * @param id The id. + * @param members The membership flag. + */ + public IdAssignmentEvent(int id, boolean members) { + this.id = id; + this.members = members; + } + + /** + * Gets the id. + * @return The id. + */ + public int getId() { + return id; + } + + /** + * Gets the membership flag. + * @return The membership flag. + */ + public boolean isMembers() { + return members; + } + +} diff --git a/src/org/apollo/game/event/impl/ItemActionEvent.java b/src/org/apollo/game/event/impl/ItemActionEvent.java new file mode 100644 index 000000000..a48015ae6 --- /dev/null +++ b/src/org/apollo/game/event/impl/ItemActionEvent.java @@ -0,0 +1,77 @@ +package org.apollo.game.event.impl; + +import org.apollo.game.event.Event; + +/** + * An {@link Event} which represents some sort of action on an item. + * @author Graham + */ +public abstract class ItemActionEvent extends Event { + + /** + * The option number (1-5). + */ + private final int option; + + /** + * The interface id. + */ + private final int interfaceId; + + /** + * The item id. + */ + private final int id; + + /** + * The item's slot. + */ + private final int slot; + + /** + * Creates the item action event. + * @param option The option number. + * @param interfaceId The interface id. + * @param id The id. + * @param slot The slot. + */ + public ItemActionEvent(int option, int interfaceId, int id, int slot) { + this.option = option; + this.interfaceId = interfaceId; + this.id = id; + this.slot = slot; + } + + /** + * Gets the option number. + * @return The option number. + */ + public int getOption() { + return option; + } + + /** + * Gets the interface id. + * @return The interface id. + */ + public int getInterfaceId() { + return interfaceId; + } + + /** + * Gets the item id. + * @return The item id. + */ + public int getId() { + return id; + } + + /** + * Gets the slot. + * @return The slot. + */ + public int getSlot() { + return slot; + } + +} diff --git a/src/org/apollo/game/event/impl/KeepAliveEvent.java b/src/org/apollo/game/event/impl/KeepAliveEvent.java new file mode 100644 index 000000000..0dc80f0d9 --- /dev/null +++ b/src/org/apollo/game/event/impl/KeepAliveEvent.java @@ -0,0 +1,32 @@ +package org.apollo.game.event.impl; + +import org.apollo.game.event.Event; + +/** + * An event which is periodically sent by the client to keep a connection + * alive. + * @author Graham + */ +public final class KeepAliveEvent extends Event { + + /** + * The time this event was created. + */ + private final long createdAt; + + /** + * Creates the keep alive event. + */ + public KeepAliveEvent() { + createdAt = System.currentTimeMillis(); + } + + /** + * Gets the time when this event was created. + * @return The time when this event was created. + */ + public long getCreatedAt() { + return createdAt; + } + +} diff --git a/src/org/apollo/game/event/impl/LogoutEvent.java b/src/org/apollo/game/event/impl/LogoutEvent.java new file mode 100644 index 000000000..7e43385f6 --- /dev/null +++ b/src/org/apollo/game/event/impl/LogoutEvent.java @@ -0,0 +1,11 @@ +package org.apollo.game.event.impl; + +import org.apollo.game.event.Event; + +/** + * An event sent to the client which logs it out cleanly. + * @author Graham + */ +public final class LogoutEvent extends Event { + +} diff --git a/src/org/apollo/game/event/impl/ObjectActionEvent.java b/src/org/apollo/game/event/impl/ObjectActionEvent.java new file mode 100644 index 000000000..e0d5be5a7 --- /dev/null +++ b/src/org/apollo/game/event/impl/ObjectActionEvent.java @@ -0,0 +1,63 @@ +package org.apollo.game.event.impl; + +import org.apollo.game.event.Event; +import org.apollo.game.model.Position; + +/** + * An {@link Event} which represents some sort of action at an object. + * @author Graham + */ +public abstract class ObjectActionEvent extends Event { + + /** + * The option number (1-3). + */ + private final int option; + + /** + * The object's id. + */ + private final int id; + + /** + * The object's position. + */ + private final Position position; + + /** + * Creates a new object action event. + * @param option The option number. + * @param id The id of the object. + * @param position The position of the object. + */ + public ObjectActionEvent(int option, int id, Position position) { + this.option = option; + this.id = id; + this.position = position; + } + + /** + * Gets the option number. + * @return The option number. + */ + public int getOption() { + return option; + } + + /** + * Gets the id of the object. + * @return The id of the object. + */ + public int getId() { + return id; + } + + /** + * Gets the position of the object. + * @return The position of the object. + */ + public Position getPosition() { + return position; + } + +} diff --git a/src/org/apollo/game/event/impl/OpenInterfaceEvent.java b/src/org/apollo/game/event/impl/OpenInterfaceEvent.java new file mode 100644 index 000000000..6b567e43d --- /dev/null +++ b/src/org/apollo/game/event/impl/OpenInterfaceEvent.java @@ -0,0 +1,32 @@ +package org.apollo.game.event.impl; + +import org.apollo.game.event.Event; + +/** + * An event which opens an interface. + * @author Graham + */ +public final class OpenInterfaceEvent extends Event { + + /** + * The interface id. + */ + private final int id; + + /** + * Creates the event with the specified interface id. + * @param id The interface id. + */ + public OpenInterfaceEvent(int id) { + this.id = id; + } + + /** + * Gets the interface id. + * @return The interface id. + */ + public int getId() { + return id; + } + +} diff --git a/src/org/apollo/game/event/impl/OpenInterfaceSidebarEvent.java b/src/org/apollo/game/event/impl/OpenInterfaceSidebarEvent.java new file mode 100644 index 000000000..b627cc011 --- /dev/null +++ b/src/org/apollo/game/event/impl/OpenInterfaceSidebarEvent.java @@ -0,0 +1,47 @@ +package org.apollo.game.event.impl; + +import org.apollo.game.event.Event; + +/** + * An {@link Event} sent to open an interface and temporary sidebar overlay. + * @author Graham + */ +public final class OpenInterfaceSidebarEvent extends Event { + + /** + * The interface id. + */ + private final int interfaceId; + + /** + * The sidebar id. + */ + private final int sidebarId; + + /** + * Creates the open interface sidebar event. + * @param interfaceId The interface id. + * @param sidebarId The sidebar id. + */ + public OpenInterfaceSidebarEvent(int interfaceId, int sidebarId) { + this.interfaceId = interfaceId; + this.sidebarId = sidebarId; + } + + /** + * Gets the interface id. + * @return The interface id. + */ + public int getInterfaceId() { + return interfaceId; + } + + /** + * Gets the sidebar id. + * @return The sidebar id. + */ + public int getSidebarId() { + return sidebarId; + } + +} diff --git a/src/org/apollo/game/event/impl/PlayerSynchronizationEvent.java b/src/org/apollo/game/event/impl/PlayerSynchronizationEvent.java new file mode 100644 index 000000000..1654355e7 --- /dev/null +++ b/src/org/apollo/game/event/impl/PlayerSynchronizationEvent.java @@ -0,0 +1,111 @@ +package org.apollo.game.event.impl; + +import java.util.List; + +import org.apollo.game.event.Event; +import org.apollo.game.model.Position; +import org.apollo.game.sync.seg.SynchronizationSegment; + +/** + * An event which is sent to synchronize the players. + * @author Graham + */ +public final class PlayerSynchronizationEvent extends Event { + + /** + * The last known region. + */ + private final Position lastKnownRegion; + + /** + * The player's position. + */ + private final Position position; + + /** + * A flag indicating if the region has changed. + */ + private final boolean regionChanged; + + /** + * The current player's synchronization segment. + */ + private final SynchronizationSegment segment; + + /** + * The number of local players. + */ + private final int localPlayers; + + /** + * A list of segments. + */ + private final List segments; + + /** + * Creates the player synchronization event. + * @param lastKnownRegion The last known region. + * @param position The player's current position. + * @param regionChanged A flag indicating if the region has changed. + * @param segment The current player's synchronization segment. + * @param localPlayers The number of local players. + * @param segments A list of segments. + */ + public PlayerSynchronizationEvent(Position lastKnownRegion, Position position, boolean regionChanged, SynchronizationSegment segment, int localPlayers, List segments) { + this.lastKnownRegion = lastKnownRegion; + this.position = position; + this.regionChanged = regionChanged; + this.segment = segment; + this.localPlayers = localPlayers; + this.segments = segments; + } + + /** + * Gets the last known region. + * @return The last known region. + */ + public Position getLastKnownRegion() { + return lastKnownRegion; + } + + /** + * Gets the player's position. + * @return The player's position. + */ + public Position getPosition() { + return position; + } + + /** + * Checks if the region has changed. + * @return {@code true} if so, {@code false} if not. + */ + public boolean hasRegionChanged() { + return regionChanged; + } + + /** + * Gets the current player's segment. + * @return The current player's segment. + */ + public SynchronizationSegment getSegment() { + return segment; + } + + /** + * Gets the number of local players. + * @return The number of local players. + */ + public int getLocalPlayers() { + return localPlayers; + } + + /** + * Gets the synchronization segments. + * @return The segments. + */ + public List getSegments() { + return segments; + } + +} diff --git a/src/org/apollo/game/event/impl/RegionChangeEvent.java b/src/org/apollo/game/event/impl/RegionChangeEvent.java new file mode 100644 index 000000000..192cf343e --- /dev/null +++ b/src/org/apollo/game/event/impl/RegionChangeEvent.java @@ -0,0 +1,33 @@ +package org.apollo.game.event.impl; + +import org.apollo.game.event.Event; +import org.apollo.game.model.Position; + +/** + * An event which indicates that the client should load the specified region. + * @author Graham + */ +public final class RegionChangeEvent extends Event { + + /** + * The position of the region to load. + */ + private final Position position; + + /** + * Creates the region changed event. + * @param position The position of the region. + */ + public RegionChangeEvent(Position position) { + this.position = position; + } + + /** + * Gets the position of the region to load. + * @return The position of the region to load. + */ + public Position getPosition() { + return position; + } + +} diff --git a/src/org/apollo/game/event/impl/SecondItemActionEvent.java b/src/org/apollo/game/event/impl/SecondItemActionEvent.java new file mode 100644 index 000000000..d4c5dc11d --- /dev/null +++ b/src/org/apollo/game/event/impl/SecondItemActionEvent.java @@ -0,0 +1,19 @@ +package org.apollo.game.event.impl; + +/** + * The second {@link ItemActionEvent}. + * @author Graham + */ +public final class SecondItemActionEvent extends ItemActionEvent { + + /** + * Creates the second item action event. + * @param interfaceId The interface id. + * @param id The item id. + * @param slot The item slot. + */ + public SecondItemActionEvent(int interfaceId, int id, int slot) { + super(2, interfaceId, id, slot); + } + +} diff --git a/src/org/apollo/game/event/impl/SecondObjectActionEvent.java b/src/org/apollo/game/event/impl/SecondObjectActionEvent.java new file mode 100644 index 000000000..1cc45542b --- /dev/null +++ b/src/org/apollo/game/event/impl/SecondObjectActionEvent.java @@ -0,0 +1,20 @@ +package org.apollo.game.event.impl; + +import org.apollo.game.model.Position; + +/** + * An event sent when the second option at an object is used. + * @author Graham + */ +public final class SecondObjectActionEvent extends ObjectActionEvent { + + /** + * Creates the second object action event. + * @param id The id. + * @param position The position. + */ + public SecondObjectActionEvent(int id, Position position) { + super(2, id, position); + } + +} diff --git a/src/org/apollo/game/event/impl/ServerMessageEvent.java b/src/org/apollo/game/event/impl/ServerMessageEvent.java new file mode 100644 index 000000000..9744725b6 --- /dev/null +++ b/src/org/apollo/game/event/impl/ServerMessageEvent.java @@ -0,0 +1,32 @@ +package org.apollo.game.event.impl; + +import org.apollo.game.event.Event; + +/** + * An event which is sent to the client with a server-side message. + * @author Graham + */ +public final class ServerMessageEvent extends Event { + + /** + * The message. + */ + private final String message; + + /** + * Creates the {@link ServerMessageEvent}. + * @param message The message. + */ + public ServerMessageEvent(String message) { + this.message = message; + } + + /** + * Gets the message. + * @return The message. + */ + public String getMessage() { + return message; + } + +} diff --git a/src/org/apollo/game/event/impl/SetInterfaceTextEvent.java b/src/org/apollo/game/event/impl/SetInterfaceTextEvent.java new file mode 100644 index 000000000..70d960401 --- /dev/null +++ b/src/org/apollo/game/event/impl/SetInterfaceTextEvent.java @@ -0,0 +1,47 @@ +package org.apollo.game.event.impl; + +import org.apollo.game.event.Event; + +/** + * An event sent to the client to update an interface's text. + * @author Graham + */ +public final class SetInterfaceTextEvent extends Event { + + /** + * The interface's id. + */ + private final int interfaceId; + + /** + * The text. + */ + private final String text; + + /** + * Creates the set interface text event. + * @param interfaceId The interface's id. + * @param text The interface's text. + */ + public SetInterfaceTextEvent(int interfaceId, String text) { + this.interfaceId = interfaceId; + this.text = text; + } + + /** + * Gets the interface id. + * @return The interface id. + */ + public int getInterfaceId() { + return interfaceId; + } + + /** + * Gets the interface's text. + * @return The interface's text. + */ + public String getText() { + return text; + } + +} diff --git a/src/org/apollo/game/event/impl/SwitchItemEvent.java b/src/org/apollo/game/event/impl/SwitchItemEvent.java new file mode 100644 index 000000000..c9bce9f4b --- /dev/null +++ b/src/org/apollo/game/event/impl/SwitchItemEvent.java @@ -0,0 +1,86 @@ +package org.apollo.game.event.impl; + +import org.apollo.game.event.Event; + +/** + * An event sent by the client when two items are switched. + * @author Graham + */ +public final class SwitchItemEvent extends Event { + + /** + * The interface id. + */ + private final int interfaceId; + + /** + * A flag indicating if insertion mode is enabled. + */ + private final boolean inserting; + + /** + * The old slot. + */ + private final int oldSlot; + + /** + * The new slot. + */ + private final int newSlot; + + /** + * Creates a new switch item event. + * @param interfaceId The interface id. + * @param inserting A flag indicating if the interface is in 'insert' mode + * instead of swap mode. + * @param oldSlot The old slot. + * @param newSlot The new slot. + */ + public SwitchItemEvent(int interfaceId, boolean inserting, int oldSlot, int newSlot) { + this.interfaceId = interfaceId; + this.inserting = inserting; + this.oldSlot = oldSlot; + this.newSlot = newSlot; + } + + /** + * Gets the interface id. + * @return The interface id. + */ + public int getInterfaceId() { + return interfaceId; + } + + /** + * Checks if this event is in insertion mode. + * @return The insertion flag. + */ + public boolean isInserting() { + return inserting; + } + + /** + * Checks if this event is in swap mode. + * @return The swap flag. + */ + public boolean isSwapping() { + return !inserting; + } + + /** + * Gets the old slot. + * @return The old slot. + */ + public int getOldSlot() { + return oldSlot; + } + + /** + * Gets the new slot. + * @return The new slot. + */ + public int getNewSlot() { + return newSlot; + } + +} diff --git a/src/org/apollo/game/event/impl/SwitchTabInterfaceEvent.java b/src/org/apollo/game/event/impl/SwitchTabInterfaceEvent.java new file mode 100644 index 000000000..9e5a4bfa0 --- /dev/null +++ b/src/org/apollo/game/event/impl/SwitchTabInterfaceEvent.java @@ -0,0 +1,47 @@ +package org.apollo.game.event.impl; + +import org.apollo.game.event.Event; + +/** + * An event sent to the client to change the interface of a tab. + * @author Graham + */ +public final class SwitchTabInterfaceEvent extends Event { + + /** + * The tab id. + */ + private final int tab; + + /** + * The interface id. + */ + private final int interfaceId; + + /** + * Creates the switch interface event. + * @param tab The tab id. + * @param interfaceId The interface id. + */ + public SwitchTabInterfaceEvent(int tab, int interfaceId) { + this.tab = tab; + this.interfaceId = interfaceId; + } + + /** + * Gets the tab id. + * @return The tab id. + */ + public int getTabId() { + return tab; + } + + /** + * Gets the interface id. + * @return The interface id. + */ + public int getInterfaceId() { + return interfaceId; + } + +} diff --git a/src/org/apollo/game/event/impl/ThirdItemActionEvent.java b/src/org/apollo/game/event/impl/ThirdItemActionEvent.java new file mode 100644 index 000000000..7bc1f0b26 --- /dev/null +++ b/src/org/apollo/game/event/impl/ThirdItemActionEvent.java @@ -0,0 +1,19 @@ +package org.apollo.game.event.impl; + +/** + * The third {@link ItemActionEvent}. + * @author Graham + */ +public final class ThirdItemActionEvent extends ItemActionEvent { + + /** + * Creates the third item action event. + * @param interfaceId The interface id. + * @param id The item id. + * @param slot The item slot. + */ + public ThirdItemActionEvent(int interfaceId, int id, int slot) { + super(3, interfaceId, id, slot); + } + +} diff --git a/src/org/apollo/game/event/impl/ThirdObjectActionEvent.java b/src/org/apollo/game/event/impl/ThirdObjectActionEvent.java new file mode 100644 index 000000000..577c5cf6c --- /dev/null +++ b/src/org/apollo/game/event/impl/ThirdObjectActionEvent.java @@ -0,0 +1,20 @@ +package org.apollo.game.event.impl; + +import org.apollo.game.model.Position; + +/** + * An event sent when the third option at an object is used. + * @author Graham + */ +public final class ThirdObjectActionEvent extends ObjectActionEvent { + + /** + * Creates the third object action event. + * @param id The id. + * @param position The position. + */ + public ThirdObjectActionEvent(int id, Position position) { + super(3, id, position); + } + +} diff --git a/src/org/apollo/game/event/impl/UpdateItemsEvent.java b/src/org/apollo/game/event/impl/UpdateItemsEvent.java new file mode 100644 index 000000000..9536333a2 --- /dev/null +++ b/src/org/apollo/game/event/impl/UpdateItemsEvent.java @@ -0,0 +1,48 @@ +package org.apollo.game.event.impl; + +import org.apollo.game.event.Event; +import org.apollo.game.model.Item; + +/** + * An event which updates all the items in an interface. + * @author Graham + */ +public final class UpdateItemsEvent extends Event { + + /** + * The interface id. + */ + private final int interfaceId; + + /** + * The items. + */ + private final Item[] items; + + /** + * Creates the update inventory interface event. + * @param interfaceId The interface id. + * @param items The items. + */ + public UpdateItemsEvent(int interfaceId, Item[] items) { + this.interfaceId = interfaceId; + this.items = items; + } + + /** + * Gets the interface id. + * @return The interface id. + */ + public int getInterfaceId() { + return interfaceId; + } + + /** + * Gets the items. + * @return The items. + */ + public Item[] getItems() { + return items; + } + +} diff --git a/src/org/apollo/game/event/impl/UpdateSkillEvent.java b/src/org/apollo/game/event/impl/UpdateSkillEvent.java new file mode 100644 index 000000000..d628b012c --- /dev/null +++ b/src/org/apollo/game/event/impl/UpdateSkillEvent.java @@ -0,0 +1,48 @@ +package org.apollo.game.event.impl; + +import org.apollo.game.event.Event; +import org.apollo.game.model.Skill; + +/** + * An {@link Event} sent to the client to update a player's skill level. + * @author Graham + */ +public final class UpdateSkillEvent extends Event { + + /** + * The skill's id. + */ + private final int id; + + /** + * The skill. + */ + private final Skill skill; + + /** + * Creates an update skill event. + * @param id The id. + * @param skill The skill. + */ + public UpdateSkillEvent(int id, Skill skill) { + this.id = id; + this.skill = skill; + } + + /** + * Gets the skill's id. + * @return The skill's id. + */ + public int getId() { + return id; + } + + /** + * Gets the skill. + * @return The skill. + */ + public Skill getSkill() { + return skill; + } + +} diff --git a/src/org/apollo/game/event/impl/UpdateSlottedItemsEvent.java b/src/org/apollo/game/event/impl/UpdateSlottedItemsEvent.java new file mode 100644 index 000000000..378fda544 --- /dev/null +++ b/src/org/apollo/game/event/impl/UpdateSlottedItemsEvent.java @@ -0,0 +1,48 @@ +package org.apollo.game.event.impl; + +import org.apollo.game.event.Event; +import org.apollo.game.model.SlottedItem; + +/** + * An event which updates a single item in an interface. + * @author Graham + */ +public final class UpdateSlottedItemsEvent extends Event { + + /** + * The interface id. + */ + private final int interfaceId; + + /** + * The slotted items. + */ + private final SlottedItem[] items; + + /** + * Creates the update item in interface event. + * @param interfaceId The interface id. + * @param items The slotted items. + */ + public UpdateSlottedItemsEvent(int interfaceId, SlottedItem... items) { + this.interfaceId = interfaceId; + this.items = items; + } + + /** + * Gets the interface id. + * @return The interface id. + */ + public int getInterfaceId() { + return interfaceId; + } + + /** + * Gets an array of slotted items. + * @return The slotted items. + */ + public SlottedItem[] getSlottedItems() { + return items; + } + +} diff --git a/src/org/apollo/game/event/impl/WalkEvent.java b/src/org/apollo/game/event/impl/WalkEvent.java new file mode 100644 index 000000000..819da6165 --- /dev/null +++ b/src/org/apollo/game/event/impl/WalkEvent.java @@ -0,0 +1,51 @@ +package org.apollo.game.event.impl; + +import org.apollo.game.event.Event; +import org.apollo.game.model.Position; + +/** + * An event which the client sends to request that the player walks somewhere. + * @author Graham + */ +public final class WalkEvent extends Event { + + /** + * The steps. + */ + private final Position[] steps; + + /** + * The running flag. + */ + private boolean run; + + /** + * Creates the event. + * @param steps The steps array. + * @param run The run flag. + */ + public WalkEvent(Position[] steps, boolean run) { + if (steps.length < 0) { + throw new IllegalArgumentException("number of steps must not be negative"); + } + this.steps = steps; + this.run = run; + } + + /** + * Gets the steps array. + * @return An array of steps. + */ + public Position[] getSteps() { + return steps; + } + + /** + * Checks if the steps should be ran (ctrl+click). + * @return {@code true} if so, {@code false} otherwise. + */ + public boolean isRunning() { + return run; + } + +} diff --git a/src/org/apollo/game/event/impl/package-info.java b/src/org/apollo/game/event/impl/package-info.java new file mode 100644 index 000000000..af5a3a650 --- /dev/null +++ b/src/org/apollo/game/event/impl/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains event implementations. + */ +package org.apollo.game.event.impl; diff --git a/src/org/apollo/game/event/package-info.java b/src/org/apollo/game/event/package-info.java new file mode 100644 index 000000000..6ca5b084b --- /dev/null +++ b/src/org/apollo/game/event/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains classes related to the event management in the game. + */ +package org.apollo.game.event; diff --git a/src/org/apollo/game/model/Animation.java b/src/org/apollo/game/model/Animation.java new file mode 100644 index 000000000..9f04eaa3f --- /dev/null +++ b/src/org/apollo/game/model/Animation.java @@ -0,0 +1,198 @@ +package org.apollo.game.model; + +/** + * Represents an animation. + * @author Graham + */ +public final class Animation { + + /** + * A special animation which stops the current animation. + */ + public static final Animation STOP_ANIMATION = new Animation(-1); + + /** + * The yes animation. + */ + public static final Animation YES = new Animation(855); + + /** + * The no animation. + */ + public static final Animation NO = new Animation(856); + + /** + * The thinking animation. + */ + public static final Animation THINKING = new Animation(857); + + /** + * The bow animation. + */ + public static final Animation BOW = new Animation(858); + + /** + * The angry animation. + */ + public static final Animation ANGRY = new Animation(859); + + /** + * The cry animation. + */ + public static final Animation CRY = new Animation(860); + + /** + * The laugh animation. + */ + public static final Animation LAUGH = new Animation(861); + + /** + * The cheer animation. + */ + public static final Animation CHEER = new Animation(862); + + /** + * The wave animation. + */ + public static final Animation WAVE = new Animation(863); + + /** + * The beckon animation. + */ + public static final Animation BECKON = new Animation(864); + + /** + * The clap animation. + */ + public static final Animation CLAP = new Animation(865); + + /** + * The dance animation. + */ + public static final Animation DANCE = new Animation(866); + + /** + * The panic animation. + */ + public static final Animation PANIC = new Animation(2105); + + /** + * The jig animation. + */ + public static final Animation JIG = new Animation(2106); + + /** + * The spin animation. + */ + public static final Animation SPIN = new Animation(2107); + + /** + * The head bang animation. + */ + public static final Animation HEAD_BANG = new Animation(2108); + + /** + * The joy jump animation. + */ + public static final Animation JOY_JUMP = new Animation(2109); + + /** + * The raspberry animation. + */ + public static final Animation RASPBERRY = new Animation(2110); + + /** + * The yawn animation. + */ + public static final Animation YAWN = new Animation(2111); + + /** + * The salute animation. + */ + public static final Animation SALUTE = new Animation(2112); + + /** + * The shrug animation. + */ + public static final Animation SHRUG = new Animation(2113); + + /** + * The blow kiss animation. + */ + public static final Animation BLOW_KISS = new Animation(1368); + + /** + * The glass wall animation. + */ + public static final Animation GLASS_WALL = new Animation(1128); + + /** + * The lean animation. + */ + public static final Animation LEAN = new Animation(1129); + + /** + * The climb rope animation. + */ + public static final Animation CLIMB_ROPE = new Animation(1130); + + /** + * The glass box animation. + */ + public static final Animation GLASS_BOX = new Animation(1131); + + /** + * The goblin bow animation. + */ + public static final Animation GOBLIN_BOW = new Animation(2127); + + /** + * The goblin dance animation. + */ + public static final Animation GOBLIN_DANCE = new Animation(2128); + + /** + * The id. + */ + private final int id; + + /** + * The delay. + */ + private final int delay; + + /** + * Creates a new animation with no delay. + * @param id The id. + */ + public Animation(int id) { + this(id, 0); + } + + /** + * Creates a new animation. + * @param id The id. + * @param delay The delay. + */ + public Animation(int id, int delay) { + this.id = id; + this.delay = delay; + } + + /** + * Gets the animation's id. + * @return The animation's id. + */ + public int getId() { + return id; + } + + /** + * Gets the animation's delay. + * @return The animation's delay. + */ + public int getDelay() { + return delay; + } + +} diff --git a/src/org/apollo/game/model/Appearance.java b/src/org/apollo/game/model/Appearance.java new file mode 100644 index 000000000..65b4581c0 --- /dev/null +++ b/src/org/apollo/game/model/Appearance.java @@ -0,0 +1,101 @@ +package org.apollo.game.model; + +/** + * Represents the appearance of a player. + * @author Graham + */ +public final class Appearance { + + /** + * The default appearance. + */ + public static final Appearance DEFAULT_APPEARANCE = new Appearance(Gender.MALE, new int[] { 0, 10, 18, 26, 33, 36, 42 }, new int[5]); + + /** + * The player's gender. + */ + private final Gender gender; + + /** + * The array of clothing/characteristic styles. + */ + private final int[] style; + + /** + * The array of clothing/skin colours. + */ + private final int[] colors; + + /** + * Creates the appearance with the specified gender, style and colors. + * @param gender The gender. + * @param style The style. + * @param colors The colors. + */ + public Appearance(Gender gender, int[] style, int[] colors) { + if (gender == null || style == null || colors == null) { + throw new NullPointerException(); + } + if (style.length != 7) { + throw new IllegalArgumentException("the style array must have 7 elements"); + } + if (colors.length != 5) { + throw new IllegalArgumentException("the colors array must have 5 elements"); + } + this.gender = gender; + this.style = style; + this.colors = colors; + } + + /** + * Gets the gender of the player. + * @return The gender of the player. + */ + public Gender getGender() { + return gender; + } + + /** + * Checks if the player is male. + * @return {@code true} if so, {@code false} if not. + */ + public boolean isMale() { + return gender == Gender.MALE; + } + + /** + * Checks if the player is female. + * @return {@code true} if so, {@code false} if not. + */ + public boolean isFemale() { + return gender == Gender.FEMALE; + } + + /** + * Gets the player's styles. + * @return The player's styles. + */ + public int[] getStyle() { + /* + * Info on the elements of the array itself: + * + * 0 = head + * 1 = chin/beard + * 2 = chest + * 3 = arms + * 4 = hands + * 5 = legs + * 6 = feet + */ + return style; + } + + /** + * Gets the player's colors. + * @return The player's colors. + */ + public int[] getColors() { + return colors; + } + +} diff --git a/src/org/apollo/game/model/Character.java b/src/org/apollo/game/model/Character.java new file mode 100644 index 000000000..2f2f4e73f --- /dev/null +++ b/src/org/apollo/game/model/Character.java @@ -0,0 +1,366 @@ +package org.apollo.game.model; + +import java.util.ArrayList; +import java.util.List; + +import org.apollo.game.action.Action; +import org.apollo.game.event.Event; +import org.apollo.game.event.impl.ServerMessageEvent; +import org.apollo.game.model.Inventory.StackMode; +import org.apollo.game.scheduling.impl.SkillNormalizationTask; +import org.apollo.game.sync.block.SynchronizationBlock; +import org.apollo.game.sync.block.SynchronizationBlockSet; +import org.apollo.util.CharacterRepository; + +/** + * A {@link Character} is a living creature in the world, such as a player or + * NPC. + * @author Graham + */ +public abstract class Character { + + /** + * The index of this character in the {@link CharacterRepository} it + * belongs to. + */ + private int index = -1; + + /** + * Teleportation flag. + */ + private boolean teleporting = false; + + /** + * The walking queue. + */ + private final WalkingQueue walkingQueue = new WalkingQueue(this); + + /** + * The first direction. + */ + private Direction firstDirection = Direction.NONE; + + /** + * The second direction. + */ + private Direction secondDirection = Direction.NONE; + + /** + * The current position of this character. + */ + private Position position; + + /** + * A list of local players. + */ + private final List localPlayers = new ArrayList(); // TODO make a specialized collection? + + /** + * A set of {@link SynchronizationBlock}s. + */ + private SynchronizationBlockSet blockSet = new SynchronizationBlockSet(); + + /** + * The character's current action. + */ + private Action action; // TODO + + /** + * The character's inventory. + */ + private final Inventory inventory = new Inventory(InventoryConstants.INVENTORY_CAPACITY); + + /** + * The character's equipment. + */ + private final Inventory equipment = new Inventory(InventoryConstants.EQUIPMENT_CAPACITY, StackMode.STACK_ALWAYS); + + /** + * The character's bank. + */ + private final Inventory bank = new Inventory(InventoryConstants.BANK_CAPACITY, StackMode.STACK_ALWAYS); + + /** + * The character's skill set. + */ + private final SkillSet skillSet = new SkillSet(); + + /** + * Creates a new character with the specified initial position. + * @param position The initial position of this character. + */ + public Character(Position position) { + this.position = position; + init(); + } + + /** + * Initialises this character. + */ + private void init() { + World.getWorld().schedule(new SkillNormalizationTask(this)); + } + + /** + * Gets the character's inventory. + * @return The character's inventory. + */ + public Inventory getInventory() { + return inventory; + } + + /** + * Gets the character's equipment. + * @return The character's equipment. + */ + public Inventory getEquipment() { + return equipment; + } + + /** + * Gets the character's bank. + * @return The character's bank. + */ + public Inventory getBank() { + return bank; + } + + /** + * Gets the local player list. + * @return The local player list. + */ + public List getLocalPlayerList() { + return localPlayers; + } + + /** + * Checks if this player is currently teleporting. + * @return {@code true} if so, {@code false} if not. + */ + public boolean isTeleporting() { + return teleporting; + } + + /** + * Sets the teleporting flag. + * @param teleporting {@code true} if the player is teleporting, + * {@code false} if not. + */ + public void setTeleporting(boolean teleporting) { + this.teleporting = teleporting; + } + + /** + * Gets the walking queue. + * @return The walking queue. + */ + public WalkingQueue getWalkingQueue() { + return walkingQueue; + } + + /** + * Sets the next directions for this character. + * @param first The first direction. + * @param second The second direction. + */ + public void setDirections(Direction first, Direction second) { + this.firstDirection = first; + this.secondDirection = second; + } + + /** + * Gets the first direction. + * @return The first direction. + */ + public Direction getFirstDirection() { + return firstDirection; + } + + /** + * Gets the second direction. + * @return The second direction. + */ + public Direction getSecondDirection() { + return secondDirection; + } + + /** + * Gets the directions as an array. + * @return A zero, one or two element array containing the directions (in + * order). + */ + public Direction[] getDirections() { + if (firstDirection != Direction.NONE) { + if (secondDirection != Direction.NONE) { + return new Direction[] { firstDirection, secondDirection }; + } else { + return new Direction[] { firstDirection }; + } + } else { + return Direction.EMPTY_DIRECTION_ARRAY; + } + } + + /** + * Gets the position of this character. + * @return The position of this character. + */ + public Position getPosition() { + return position; + } + + /** + * Sets the position of this character. + * @param position The position of this character. + */ + public void setPosition(Position position) { + this.position = position; + } + + /** + * Checks if this character is active. + * @return {@code true} if so, {@code false} if not. + */ + public boolean isActive() { + return index != -1; + } + + /** + * Gets the index of this character. + * @return The index of this character. + */ + public int getIndex() { + synchronized (this) { + return index; + } + } + + /** + * Sets the index of this character. + * @param index The index of this character. + */ + public void setIndex(int index) { + synchronized (this) { + this.index = index; + } + } + + /** + * Gets the {@link SynchronizationBlockSet}. + * @return The block set. + */ + public SynchronizationBlockSet getBlockSet() { + return blockSet; + } + + /** + * Resets the block set. + */ + public void resetBlockSet() { + blockSet = new SynchronizationBlockSet(); + } + + /** + * Sends an {@link Event} to either: + *

    + *
  • The client if this {@link Character} is a {@link Player}.
  • + *
  • The AI routines if this {@link Character} is an NPC
  • + *
+ * @param event The event. + */ + public abstract void send(Event event); + + /** + * Teleports this character to the specified position, setting the + * appropriate flags and clearing the walking queue. + * @param position The position. + */ + public void teleport(Position position) { + this.teleporting = true; + this.position = position; + this.walkingQueue.clear(); + this.stopAction(); // TODO do it on any movement is a must.. walking queue perhaps? + } + + /** + * Plays the specified animation. + * @param animation The animation. + */ + public void playAnimation(Animation animation) { + blockSet.add(SynchronizationBlock.createAnimationBlock(animation)); + } + + /** + * Stops the current animation. + */ + public void stopAnimation() { + playAnimation(Animation.STOP_ANIMATION); + } + + /** + * Plays the specified graphic. + * @param graphic The graphic. + */ + public void playGraphic(Graphic graphic) { + blockSet.add(SynchronizationBlock.createGraphicBlock(graphic)); + } + + /** + * Stops the current graphic. + */ + public void stopGraphic() { + playGraphic(Graphic.STOP_GRAPHIC); + } + + /** + * Gets the character's skill set. + * @return The character's skill set. + */ + public SkillSet getSkillSet() { + return skillSet; + } + + /** + * Starts a new action, stopping the current one if it exists. + * @param action The new action. + * @return A flag indicating if the action was started. + */ + public boolean startAction(Action action) { + if (this.action != null) { + if (this.action.equals(action)) { + return false; + } + stopAction(); + } + this.action = action; + World.getWorld().schedule(action); + return true; // TODO maybe this should be incorporated into the action class itself? + } + + /** + * Stops the current action. + */ + public void stopAction() { + if (action != null) { + action.stop(); + action = null; + } + } + + /** + * Turns the character to face the specified position. + * @param position The position to face. + */ + public void turnTo(Position position) { + blockSet.add(SynchronizationBlock.createTurnToPositionBlock(position)); + } + + /** + * Sends a message to the character. + * @param message The message. + */ + public void sendMessage(String message) { + send(new ServerMessageEvent(message)); + } + +} diff --git a/src/org/apollo/game/model/Direction.java b/src/org/apollo/game/model/Direction.java new file mode 100644 index 000000000..dca39c2a7 --- /dev/null +++ b/src/org/apollo/game/model/Direction.java @@ -0,0 +1,124 @@ +package org.apollo.game.model; + +/** + * Represents a single movement direction. + * @author Graham + */ +public enum Direction { + + /** + * North movement. + */ + NORTH(1), + + /** + * North east movement. + */ + NORTH_EAST(2), + + /** + * East movement. + */ + EAST(4), + + /** + * South east movement. + */ + SOUTH_EAST(7), + + /** + * South movement. + */ + SOUTH(6), + + /** + * South west movement. + */ + SOUTH_WEST(5), + + /** + * West movement. + */ + WEST(3), + + /** + * North west movement. + */ + NORTH_WEST(0), + + /** + * No movement. + */ + NONE(-1); + + /** + * An empty direction array. + */ + public static final Direction[] EMPTY_DIRECTION_ARRAY = new Direction[0]; + + /** + * Checks if the direction represented by the two delta values can connect + * two points together in a single direction. + * @param deltaX The difference in X coordinates. + * @param deltaY The difference in X coordinates. + * @return {@code true} if so, {@code false} if not. + */ + public static boolean isConnectable(int deltaX, int deltaY) { + return Math.abs(deltaX) == Math.abs(deltaY) || deltaX == 0 || deltaY == 0; + } + + /** + * Creates a direction from the differences between X and Y. + * @param deltaX The difference between two X coordinates. + * @param deltaY The difference between two Y coordinates. + * @return The direction. + */ + public static Direction fromDeltas(int deltaX, int deltaY) { + if (deltaY == 1) { + if (deltaX == 1) { + return Direction.NORTH_EAST; + } else if (deltaX == 0) { + return Direction.NORTH; + } else { + return Direction.NORTH_WEST; + } + } else if (deltaY == -1) { + if (deltaX == 1) { + return Direction.SOUTH_EAST; + } else if (deltaX == 0) { + return Direction.SOUTH; + } else { + return Direction.SOUTH_WEST; + } + } else { + if (deltaX == 1) { + return Direction.EAST; + } else if (deltaX == -1) { + return Direction.WEST; + } + } + return Direction.NONE; + } + + /** + * The direction as an integer. + */ + private final int intValue; + + /** + * Creates the direction. + * @param intValue The direction as an integer. + */ + private Direction(int intValue) { + this.intValue = intValue; + } + + /** + * Gets the direction as an integer which the client can understand. + * @return The movement as an integer. + */ + public int toInteger() { + return intValue; + } + +} diff --git a/src/org/apollo/game/model/EquipmentConstants.java b/src/org/apollo/game/model/EquipmentConstants.java new file mode 100644 index 000000000..8ebff71f6 --- /dev/null +++ b/src/org/apollo/game/model/EquipmentConstants.java @@ -0,0 +1,71 @@ +package org.apollo.game.model; + +/** + * Contains equipment-related constants. + * @author Graham + */ +public final class EquipmentConstants { + + /** + * The hat slot. + */ + public static final int HAT = 0; + + /** + * The cape slot. + */ + public static final int CAPE = 1; + + /** + * The amulet slot. + */ + public static final int AMULET = 2; + + /** + * The weapon slot. + */ + public static final int WEAPON = 3; + + /** + * The chest slot. + */ + public static final int CHEST = 4; + + /** + * The shield slot. + */ + public static final int SHIELD = 5; + + /** + * The legs slot. + */ + public static final int LEGS = 7; + + /** + * The hands slot. + */ + public static final int HANDS = 9; + + /** + * The feet slot. + */ + public static final int FEET = 10; + + /** + * The ring slot. + */ + public static final int RING = 12; + + /** + * The arrows slot. + */ + public static final int ARROWS = 13; + + /** + * Default private constructor to prevent instantiation; + */ + private EquipmentConstants() { + + } + +} diff --git a/src/org/apollo/game/model/Gender.java b/src/org/apollo/game/model/Gender.java new file mode 100644 index 000000000..63ee6f598 --- /dev/null +++ b/src/org/apollo/game/model/Gender.java @@ -0,0 +1,40 @@ +package org.apollo.game.model; + +/** + * An enumeration containing the two genders (male and female). + * @author Graham + */ +public enum Gender { + + /** + * The male gender. + */ + MALE(0), + + /** + * The female gender. + */ + FEMALE(1); + + /** + * An integer representation used by the client. + */ + private final int intValue; + + /** + * Creates the gender. + * @param intValue The integer representation. + */ + private Gender(int intValue) { + this.intValue = intValue; + } + + /** + * Converts this gender to an integer. + * @return The integer representation used by the client. + */ + public int toInteger() { + return intValue; + } + +} diff --git a/src/org/apollo/game/model/Graphic.java b/src/org/apollo/game/model/Graphic.java new file mode 100644 index 000000000..7a521b478 --- /dev/null +++ b/src/org/apollo/game/model/Graphic.java @@ -0,0 +1,82 @@ +package org.apollo.game.model; + +/** + * Represents a 'still graphic'. + * @author Graham + */ +public final class Graphic { + + /** + * A special graphic which stops the current graphic. + */ + public static final Graphic STOP_GRAPHIC = new Graphic(-1); + + /** + * The id. + */ + private final int id; + + /** + * The delay. + */ + private final int delay; + + /** + * The height. + */ + private final int height; + + /** + * Creates a new graphic with no delay and a height of zero. + * @param id The id. + */ + public Graphic(int id) { + this(id, 0, 0); + } + + /** + * Creates a new graphic with a height of zero. + * @param id The id. + * @param delay The delay. + */ + public Graphic(int id, int delay) { + this(id, delay, 0); + } + + /** + * Creates a new graphic. + * @param id The id. + * @param delay The delay. + * @param height The height. + */ + public Graphic(int id, int delay, int height) { + this.id = id; + this.delay = delay; + this.height = height; + } + + /** + * Gets the graphic's id. + * @return The graphic's id. + */ + public int getId() { + return id; + } + + /** + * Gets the graphic's delay. + * @return The graphic's delay. + */ + public int getDelay() { + return delay; + } + + /** + * Gets the graphic's height. + * @return The graphic's height. + */ + public int getHeight() { + return height; + } + +} diff --git a/src/org/apollo/game/model/InterfaceSet.java b/src/org/apollo/game/model/InterfaceSet.java new file mode 100644 index 000000000..3b86e37f4 --- /dev/null +++ b/src/org/apollo/game/model/InterfaceSet.java @@ -0,0 +1,176 @@ +package org.apollo.game.model; + +import java.util.HashMap; +import java.util.Map; + +import org.apollo.game.event.impl.CloseInterfaceEvent; +import org.apollo.game.event.impl.EnterAmountEvent; +import org.apollo.game.event.impl.OpenInterfaceEvent; +import org.apollo.game.event.impl.OpenInterfaceSidebarEvent; +import org.apollo.game.model.inter.EnterAmountListener; +import org.apollo.game.model.inter.InterfaceListener; + +/** + * Represents the set of interfaces the player has open. + *

+ * This class manages all six distinct types of interface (the last two are not + * present on 317 servers). + *

+ *

    + *
  • Windows: the ones people mostly associate with the + * word interfaces. Things like your bank, the wildy warning screen, the + * trade screen, etc.
  • + *
  • Overlays: display in the same place as windows, but + * don't prevent you from moving. For example, the wilderness level + * indicator.
  • + *
  • Dialogues: interfaces which are displayed over the + * chat box.
  • + *
  • Sidebars: an interface which displays over the + * inventory area.
  • + *
  • Fullscreen windows: a window which displays over the + * whole screen e.g. the 377 welcome screen.
  • + *
  • Fullscreen background: an interface displayed behind + * the fullscreen window, typically a blank, black screen.
  • + *
+ * @author Graham + */ +public final class InterfaceSet { + + /** + * The player whose interfaces are being managed. + */ + private final Player player; // TODO: maybe switch to a listener system like the inventory? + + // TODO: maybe store the current inventory tab ids here?? + /** + * A map of open interfaces. + */ + private Map interfaces = new HashMap(); + + /** + * The current listener. + */ + private InterfaceListener listener; + + /** + * The current enter amount listener. + */ + private EnterAmountListener amountListener; + + /** + * Creates an interface set. + * @param player The player. + */ + public InterfaceSet(Player player) { + this.player = player; + } + + /** + * Closes the current open interface(s). + */ + public void close() { + closeAndNotify(); + + player.send(new CloseInterfaceEvent()); + } + + /** + * Sent by the client when it has closed an interface. + */ + public void interfaceClosed() { + closeAndNotify(); + } + + /** + * Opens the enter amount dialog. + * @param listener The enter amount listener. + */ + public void openEnterAmountDialog(EnterAmountListener listener) { + this.amountListener = listener; + + player.send(new EnterAmountEvent()); + } + + /** + * Opens a window and inventory sidebar. + * @param windowId The window's id. + * @param sidebarId The sidebar's id. + */ + public void openWindowWithSidebar(int windowId, int sidebarId) { + openWindowWithSidebar(null, windowId, sidebarId); + } + + /** + * Opens a window and inventory sidebar with the specified listener. + * @param listener The listener for this interface. + * @param windowId The window's id. + * @param sidebarId The sidebar's id. + */ + public void openWindowWithSidebar(InterfaceListener listener, int windowId, int sidebarId) { + closeAndNotify(); + this.listener = listener; + + interfaces.put(InterfaceType.WINDOW, windowId); + interfaces.put(InterfaceType.SIDEBAR, sidebarId); + + player.send(new OpenInterfaceSidebarEvent(windowId, sidebarId)); + } + + /** + * Opens a window. + * @param windowId The window's id. + */ + public void openWindow(int windowId) { + openWindow(null, windowId); + } + + /** + * Opens a window with the specified listener. + * @param listener The listener for this interface. + * @param windowId The window's id. + */ + public void openWindow(InterfaceListener listener, int windowId) { + closeAndNotify(); + this.listener = listener; + + interfaces.put(InterfaceType.WINDOW, windowId); + + player.send(new OpenInterfaceEvent(windowId)); + } + + /** + * Checks if this interface sets contains the specified interface. + * @param id The interface's id. + * @return {@code true} if so, {@code false} if not. + */ + public boolean contains(int id) { + return interfaces.containsValue(id); + } + + /** + * An internal method for closing the interface, notifying the listener if + * appropriate, but not sending any events. + */ + private void closeAndNotify() { + amountListener = null; // TODO should we notify?? + + interfaces.clear(); + if (listener != null) { + listener.interfaceClosed(); + listener = null; + } + } + + /** + * Called when the client has entered the specified amount. Notifies the + * current listener. + * @param amount The amount. + */ + public void enteredAmount(int amount) { + if (amountListener != null) { + amountListener.amountEntered(amount); + amountListener = null; + } + } + +} diff --git a/src/org/apollo/game/model/InterfaceType.java b/src/org/apollo/game/model/InterfaceType.java new file mode 100644 index 000000000..4eb42cf3f --- /dev/null +++ b/src/org/apollo/game/model/InterfaceType.java @@ -0,0 +1,40 @@ +package org.apollo.game.model; + +/** + * Represents the different types of interfaces. + * @author Graham + */ +public enum InterfaceType { + + /** + * A window is an interface which occupies the game screen. + */ + WINDOW, + + /** + * An overlay is an interface which occupies the game screen like a window, + * however, you can walk around and perform actions still. + */ + OVERLAY, + + /** + * A dialogue is an interface which appears in the chat box. + */ + DIALOGUE, + + /** + * An interface which displays over the inventory area. + */ + SIDEBAR, + + /** + * An interface which is shown in full screen mode. + */ + FULLSCREEN_WINDOW, + + /** + * An interface which is shown behind a fullscreen window. + */ + FULLSCREEN_BACKGROUND; + +} diff --git a/src/org/apollo/game/model/Inventory.java b/src/org/apollo/game/model/Inventory.java new file mode 100644 index 000000000..5dcfd6022 --- /dev/null +++ b/src/org/apollo/game/model/Inventory.java @@ -0,0 +1,553 @@ +package org.apollo.game.model; + +import java.util.ArrayList; +import java.util.List; + +import org.apollo.game.model.def.ItemDefinition; +import org.apollo.game.model.inv.InventoryListener; + +/** + * Represents an inventory - a collection of {@link Item}s. + * @author Graham + */ +public final class Inventory implements Cloneable { + + /** + * An enumeration containing the different 'stacking modes' of an + * {@link Inventory}. + * @author Graham + */ + public enum StackMode { + + /** + * When in {@link #STACK_ALWAYS} mode, an {@link Inventory} will stack + * every single item, regardless of the settings of individual items. + */ + STACK_ALWAYS, + + /** + * When in {@link #STACK_STACKABLE_ITEMS} mode, an {@link Inventory} + * will stack items depending on their settings. + */ + STACK_STACKABLE_ITEMS, + + /** + * When in {@link #STACK_NEVER} mode, an {@link Inventory} will never + * stack items. + */ + STACK_NEVER; + + } + + /** + * A list of inventory listeners. + */ + private final List listeners = new ArrayList(); + + /** + * The capacity of this inventory. + */ + private final int capacity; + + /** + * The items in this inventory. + */ + private Item[] items; + + /** + * The stacking mode. + */ + private final StackMode mode; + + /** + * The size of this inventory - the number of 'used slots'. + */ + private int size = 0; + + /** + * A flag indicating if events are being fired. + */ + private boolean firingEvents = true; // TODO: make this reentrant + + /** + * Creates an inventory. + * @param capacity The capacity. + * @throws IllegalArgumentException if the capacity is negative. + */ + public Inventory(int capacity) { + this(capacity, StackMode.STACK_STACKABLE_ITEMS); + } + + /** + * Creates an inventory. + * @param capacity The capacity. + * @param mode The stacking mode. + * @throws IllegalArgumentException if the capacity is negative. + * @throws NullPointerException if the mode is {@code null}. + */ + public Inventory(int capacity, StackMode mode) { + if (capacity < 0) { + throw new IllegalArgumentException("capacity cannot be negative"); + } + if (mode == null) { + throw new NullPointerException("mode"); + } + this.capacity = capacity; + this.items = new Item[capacity]; + this.mode = mode; + } + + /** + * Creates a copy of this inventory. Listeners are not copied, they must be + * added again yourself! This is so cloned copies don't send updates to + * their counterparts. + */ + @Override + public Inventory clone() { + Inventory copy = new Inventory(capacity, mode); + System.arraycopy(items, 0, copy.items, 0, capacity); + copy.size = size; + return copy; + } + + /** + * Checks if this inventory contains an item with the specified id. + * @param id The item's id. + * @return {@code true} if so, {@code false} if not. + */ + public boolean contains(int id) { + for (int i = 0; i < capacity; i++) { + Item item = items[i]; + if (item != null && item.getId() == id) { + return true; + } + } + return false; + } + + /** + * Gets the number of free slots. + * @return The number of free slots. + */ + public int freeSlots() { + return capacity - size; + } + + /** + * Clears the inventory. + */ + public void clear() { + items = new Item[capacity]; + size = 0; + notifyItemsUpdated(); + } + + /** + * Gets the capacity of this inventory. + * @return The capacity. + */ + public int capacity() { + return capacity; + } + + /** + * Gets the size of this inventory - the number of used slots. + * @return The size. + */ + public int size() { + return size; + } + + /** + * Gets the item in the specified slot. + * @param slot The slot. + * @return The item, or {@code null} if the slot is empty. + * @throws IndexOutOfBoundsException if the slot is out of bounds. + */ + public Item get(int slot) { + checkBounds(slot); + return items[slot]; + } + + /** + * Sets the item that is in the specified slot. + * @param slot The slot. + * @param item The item, or {@code null} to remove the item that is in the + * slot. + * @return The item that was in the slot. + * @throws IndexOutOfBoundsException if the slot is out of bounds. + */ + public Item set(int slot, Item item) { + if (item == null) { + return reset(slot); + } + checkBounds(slot); + + Item old = items[slot]; + if (old == null) { + size++; + } + items[slot] = item; + notifyItemUpdated(slot); + return old; + } + + /** + * Removes the item (if any) that is in the specified slot. + * @param slot + * @return The item that was in the slot. + * @throws IndexOutOfBoundsException if the slot is out of bounds. + */ + public Item reset(int slot) { + checkBounds(slot); + + Item old = items[slot]; + if (old != null) { + size--; + } + items[slot] = null; + notifyItemUpdated(slot); + return old; + } + + /** + * An alias for {@code add(id, 1)}. + * @param id The id. + * @return {@code true} if the item was added, {@code false} if there was + * not enough room. + */ + public boolean add(int id) { + return add(id, 1) == 0; + } + + /** + * An alias for {@code add(new Item(id, amount)}. + * @param id The id. + * @param amount The amount. + * @return The amount that remains. + */ + public int add(int id, int amount) { + Item item = add(new Item(id, amount)); + if (item != null) { + return item.getAmount(); + } + return 0; + } + + /** + * Adds an item to this inventory. This will attempt to add as much of the + * item that is possible. If the item remains, it will be returned (in the + * case of stackable items, any quantity that remains in the stack is + * returned). If nothing remains, the method will return {@code null}. If + * something remains, the listener will also be notified which could be + * used, for example, to send a message to the player. + * @param item The item to add to this inventory. + * @return The item that remains if there is not enough room in the + * inventory. If nothing remains, {@code null}. + */ + public Item add(Item item) { + int id = item.getId(); + boolean stackable = isStackable(item.getDefinition()); + if (stackable) { + for (int slot = 0; slot < capacity; slot++) { + Item other = items[slot]; + if (other != null && other.getId() == id) { + long total = item.getAmount() + other.getAmount(); + int amount; + int remaining; + if (total > Integer.MAX_VALUE) { + amount = (int) (total - Integer.MAX_VALUE); + remaining = (int) (total - amount); + notifyCapacityExceeded(); + } else { + amount = (int) total; + remaining = 0; + } + set(slot, new Item(id, amount)); + return remaining > 0 ? new Item(id, remaining): null; + } + } + for (int slot = 0; slot < capacity; slot++) { + Item other = items[slot]; + if (other == null) { + set(slot, item); + return null; + } + } + notifyCapacityExceeded(); + return item; + } + + int remaining = item.getAmount(); + + stopFiringEvents(); + try { + Item single = new Item(item.getId(), 1); + for (int slot = 0; slot < capacity; slot++) { + if (items[slot] == null) { + remaining--; + set(slot, single); // share the instances + if (remaining <= 0) { + break; + } + } + } + } finally { + startFiringEvents(); + } + + if (remaining != item.getAmount()) { + notifyItemsUpdated(); + } + if (remaining > 0) { + notifyCapacityExceeded(); + } + + return new Item(item.getId(), remaining); + } + + /** + * Removes one item with the specified id. + * @param id The id. + * @return {@code true} if the item was removed, {@code false} otherwise. + */ + public boolean remove(int id) { + return remove(id, 1) == 1; + } + + /** + * An alias for {@code remove(item.getId(), item.getAmount())}. + * @param item The item to remove. + * @return The amount that was removed. + */ + public int remove(Item item) { + return remove(item.getId(), item.getAmount()); + } + + /** + * Removes {@code amount} of the item with the specified {@code id}. If the + * item is stackable, it will remove it from the stack. If not, it'll + * remove {@code amount} items. + * @param id The id. + * @param amount The amount. + * @return The amount that was removed. + */ + public int remove(int id, int amount) { + ItemDefinition def = ItemDefinition.forId(id); + boolean stackable = isStackable(def); + if (stackable) { + for (int slot = 0; slot < capacity; slot++) { + Item item = items[slot]; + if (item != null && item.getId() == id) { + if (amount >= item.getAmount()) { + set(slot, null); + return item.getAmount(); + } else { + int newAmount = item.getAmount() - amount; + set(slot, new Item(item.getId(), newAmount)); + return amount; + } + } + } + return 0; + } + int removed = 0; + for (int slot = 0; slot < capacity; slot++) { + Item item = items[slot]; + if (item != null && item.getId() == id) { + set(slot, null); + removed++; + } + if (removed >= amount) { + break; + } + } + return removed; + } + + /** + * Shifts all items to the top left of the container, leaving no gaps. + */ + public void shift() { + Item[] old = items; + items = new Item[capacity]; + for (int i = 0, pos = 0; i < items.length; i++) { + if (old[i] != null) { + items[pos++] = old[i]; + } + } + if (firingEvents) { + notifyItemsUpdated(); + } + } + + /** + * Swaps the two items at the specified slots. + * @param oldSlot The old slot. + * @param newSlot The new slot. + * @throws IndexOutOufBoundsException if the slot is out of bounds. + */ + public void swap(int oldSlot, int newSlot) { + swap(false, oldSlot, newSlot); + } + + /** + * Swaps the two items at the specified slots. + * @param insert If the swap should be done in insertion mode. + * @param oldSlot The old slot. + * @param newSlot The new slot. + * @throws IndexOutOfBoundsException if the slot is out of bounds. + */ + public void swap(boolean insert, int oldSlot, int newSlot) { + checkBounds(oldSlot); + checkBounds(newSlot); + if (insert) { + if (newSlot > oldSlot) { + for (int slot = oldSlot; slot < newSlot; slot++) { + swap(slot, slot + 1); + } + } else if (oldSlot > newSlot) { + for (int slot = oldSlot; slot > newSlot; slot--) { + swap(slot, slot - 1); + } + } // else no change is required - aren't we lucky? + forceRefresh(); + } else { + Item temp = items[oldSlot]; + items[oldSlot] = items[newSlot]; + items[newSlot] = temp; + notifyItemsUpdated(); // TODO can we just fire for the two slots? + } + } + + /** + * Adds a listener. + * @param listener The listener to add. + */ + public void addListener(InventoryListener listener) { + listeners.add(listener); + } + + /** + * Removes a listener. + * @param listener The listener to remove. + */ + public void removeListener(InventoryListener listener) { + listeners.remove(listener); + } + + /** + * Removes all the listeners. + */ + public void removeAllListeners() { + listeners.clear(); + } + + /** + * Notifies listeners that the capacity of this inventory has been + * exceeded. + */ + private void notifyCapacityExceeded() { + if (firingEvents) { + for (InventoryListener listener : listeners) { + listener.capacityExceeded(this); + } + } + } + + /** + * Notifies listeners that all the items have been updated. + */ + private void notifyItemsUpdated() { + if (firingEvents) { + for (InventoryListener listener : listeners) { + listener.itemsUpdated(this); + } + } + } + + /** + * Notifies listeners that the specified slot has been updated. + * @param slot The slot. + */ + private void notifyItemUpdated(int slot) { + if (firingEvents) { + Item item = items[slot]; + for (InventoryListener listener : listeners) { + listener.itemUpdated(this, slot, item); + } + } + } + + /** + * Checks the bounds of the specified slot. + * @param slot The slot. + * @throws IndexOutOfBoundsException if the slot is out of bounds. + */ + private void checkBounds(int slot) { + if (slot < 0 || slot >= capacity) { + throw new IndexOutOfBoundsException("slot out of bounds"); + } + } + + /** + * Checks if the item specified by the definition should be stacked. + * @param def The definition. + * @return {@code true} if the item should be stacked, {@code false} + * otherwise. + */ + private boolean isStackable(ItemDefinition def) { + if (mode == StackMode.STACK_ALWAYS) { + return true; + } else if (mode == StackMode.STACK_STACKABLE_ITEMS) { + return def.isStackable(); + } else { // will be STACK_NEVER + return false; + } + } + + /** + * Gets a clone of the items array. + * @return A clone of the items array. + */ + public Item[] getItems() { + return items.clone(); + } + + /** + * Stops the firing of events. + */ + public void stopFiringEvents() { + firingEvents = false; + } + + /** + * Starts the firing of events. + */ + public void startFiringEvents() { + firingEvents = true; + } + + /** + * Forces the refresh of this inventory. + */ + public void forceRefresh() { + notifyItemsUpdated(); + } + + /** + * Forces a refresh of a specific slot. + * @param slot The slot. + */ + public void forceRefresh(int slot) { + notifyItemUpdated(slot); + } + + /** + * Forces the capacity to exceeded event to be fired. + */ + public void forceCapacityExceeded() { + notifyCapacityExceeded(); + } + +} diff --git a/src/org/apollo/game/model/InventoryConstants.java b/src/org/apollo/game/model/InventoryConstants.java new file mode 100644 index 000000000..5a3fd8c57 --- /dev/null +++ b/src/org/apollo/game/model/InventoryConstants.java @@ -0,0 +1,31 @@ +package org.apollo.game.model; + +/** + * Holds {@link Inventory}-related constants. + * @author Graham + */ +public final class InventoryConstants { + + /** + * The capacity of the bank. + */ + public static final int BANK_CAPACITY = 352; + + /** + * The capacity of the inventory. + */ + public static final int INVENTORY_CAPACITY = 28; + + /** + * The capacity of the equipment inventory. + */ + public static final int EQUIPMENT_CAPACITY = 14; + + /** + * Default private constructor to prevent instantiation. + */ + private InventoryConstants() { + + } + +} diff --git a/src/org/apollo/game/model/Item.java b/src/org/apollo/game/model/Item.java new file mode 100644 index 000000000..deed50f6c --- /dev/null +++ b/src/org/apollo/game/model/Item.java @@ -0,0 +1,72 @@ +package org.apollo.game.model; + +import org.apollo.game.model.def.ItemDefinition; + +/** + * Represents a single item. + * @author Graham + */ +public final class Item { + + /** + * The item's id. + */ + private final int id; + + /** + * The amount of items in the stack. + */ + private final int amount; + + /** + * Creates an item with an amount of {@code 1}. + * @param id The item's id. + */ + public Item(int id) { + this(id, 1); + } + + /** + * Creates an item with the specified the amount. + * @param id The item's id. + * @param amount The amount. + * @throws IllegalArgumentException if the amount is negative. + */ + public Item(int id, int amount) { + if (amount < 0) { + throw new IllegalArgumentException("negative amount"); + } + this.id = id; + this.amount = amount; + } + + /** + * Gets the id. + * @return The id. + */ + public int getId() { + return id; + } + + /** + * Gets the amount. + * @return The amount. + */ + public int getAmount() { + return amount; + } + + /** + * Gets the {@link ItemDefinition} which describes this item. + * @return The definition. + */ + public ItemDefinition getDefinition() { + return ItemDefinition.forId(id); + } + + @Override + public String toString() { + return Item.class.getName() + " [id=" + id + ", amount=" + amount + "]"; + } + +} diff --git a/src/org/apollo/game/model/Player.java b/src/org/apollo/game/model/Player.java new file mode 100644 index 000000000..c9f475dd3 --- /dev/null +++ b/src/org/apollo/game/model/Player.java @@ -0,0 +1,519 @@ +package org.apollo.game.model; + +import java.util.ArrayDeque; +import java.util.Queue; + +import org.apollo.game.event.Event; +import org.apollo.game.event.impl.IdAssignmentEvent; +import org.apollo.game.event.impl.LogoutEvent; +import org.apollo.game.event.impl.SwitchTabInterfaceEvent; +import org.apollo.game.model.inter.bank.BankConstants; +import org.apollo.game.model.inv.AppearanceInventoryListener; +import org.apollo.game.model.inv.FullInventoryListener; +import org.apollo.game.model.inv.InventoryListener; +import org.apollo.game.model.inv.SynchronizationInventoryListener; +import org.apollo.game.model.skill.LevelUpSkillListener; +import org.apollo.game.model.skill.SkillListener; +import org.apollo.game.model.skill.SynchronizationSkillListener; +import org.apollo.game.sync.block.SynchronizationBlock; +import org.apollo.net.session.GameSession; +import org.apollo.security.PlayerCredentials; + +/** + * A {@link Player} is a {@link Character} that a user is controlling. + * @author Graham + */ +public final class Player extends Character { + + /** + * An enumeration with the different privilege levels a player can have. + * @author Graham + */ + public enum PrivilegeLevel { + + /** + * A standard (rights 0) account. + */ + STANDARD(0), + + /** + * A player moderator (rights 1) account. + */ + MODERATOR(1), + + /** + * An administrator (rights 2) account. + */ + ADMINISTRATOR(2); + + /** + * Gets the privilege level for the specified numerical level. + * @param numericalLevel The numerical level. + * @return The privilege level. + * @throws IllegalArgumentException if the numerical level is invalid. + */ + public static PrivilegeLevel valueOf(int numericalLevel) { + for (PrivilegeLevel level : values()) { + if (level.numericalLevel == numericalLevel) { + return level; + } + } + throw new IllegalArgumentException("invalid numerical level"); + } + + /** + * The numerical level used in the protocol. + */ + private final int numericalLevel; + + /** + * Creates a privilege level. + * @param numericalLevel The numerical level. + */ + private PrivilegeLevel(int numericalLevel) { + this.numericalLevel = numericalLevel; + } + + /** + * Gets the numerical level. + * @return The numerical level used in the protocol. + */ + public int toInteger() { + return numericalLevel; + } + + } + + /** + * A temporary queue of events sent during the login process. + */ + private final Queue queuedEvents = new ArrayDeque(); + + /** + * The player's credentials. + */ + private PlayerCredentials credentials; + + /** + * The privilege level. + */ + private PrivilegeLevel privilegeLevel = PrivilegeLevel.STANDARD; + + /** + * The membership flag. + */ + private boolean members = false; + + /** + * A flag indicating if the player has designed their character. + */ + private boolean designedCharacter = false; + + /** + * The {@link GameSession} currently attached to this {@link Player}. + */ + private GameSession session; + + /** + * The centre of the last region the client has loaded. + */ + private Position lastKnownRegion; + + /** + * A flag indicating if the region changed in the last cycle. + */ + private boolean regionChanged = false; + + /** + * The player's appearance. + */ + private Appearance appearance = Appearance.DEFAULT_APPEARANCE; + + /** + * The current maximum viewing distance of this player. + */ + private int viewingDistance = 1; + + /** + * A flag which indicates there are players that couldn't be added. + */ + private boolean excessivePlayers = false; + + /** + * This player's interface set. + */ + private final InterfaceSet interfaceSet = new InterfaceSet(this); + + /** + * A flag indicating if the player is withdrawing items as notes. + */ + private boolean withdrawingNotes = false; // TODO find a better place! + + /** + * Creates the {@link Player}. + * @param credentials The player's credentials. + * @param position The initial position. + */ + public Player(PlayerCredentials credentials, Position position) { + super(position); + init(); + this.credentials = credentials; + } + + /** + * Gets this player's interface set. + * @return The interface set for this player. + */ + public InterfaceSet getInterfaceSet() { + return interfaceSet; + } + + /** + * Checks if there are excessive players. + * @return {@code true} if so, {@code false} if not. + */ + public boolean isExcessivePlayersSet() { + return excessivePlayers; + } + + /** + * Sets the excessive players flag. + */ + public void flagExcessivePlayers() { + excessivePlayers = true; + } + + /** + * Resets the excessive players flag. + */ + public void resetExcessivePlayers() { + excessivePlayers = false; + } + + /** + * Resets this player's viewing distance. + */ + public void resetViewingDistance() { + viewingDistance = 1; + } + + /** + * Gets this player's viewing distance. + * @return The viewing distance. + */ + public int getViewingDistance() { + return viewingDistance; + } + + /** + * Increments this player's viewing distance if it is less than the maximum + * viewing distance. + */ + public void incrementViewingDistance() { + if (viewingDistance < Position.MAX_DISTANCE) { + viewingDistance++; + } + } + + /** + * Decrements this player's viewing distance if it is greater than 1. + */ + public void decrementViewingDistance() { + if (viewingDistance > 1) { // TODO should it be 0? + viewingDistance--; + } + } + + /** + * Checks if this player has ever known a region. + * @return {@code true} if so, {@code false} if not. + */ + public boolean hasLastKnownRegion() { + return lastKnownRegion != null; + } + + /** + * Gets the last known region. + * @return The last known region, or {@code null} if the player has never + * known a region. + */ + public Position getLastKnownRegion() { + return lastKnownRegion; + } + + /** + * Sets the last known region. + * @param lastKnownRegion The last known region. + */ + public void setLastKnownRegion(Position lastKnownRegion) { + this.lastKnownRegion = lastKnownRegion; + } + + /** + * Gets the privilege level. + * @return The privilege level. + */ + public PrivilegeLevel getPrivilegeLevel() { + return privilegeLevel; + } + + /** + * Sets the privilege level. + * @param privilegeLevel The privilege level. + */ + public void setPrivilegeLevel(PrivilegeLevel privilegeLevel) { + this.privilegeLevel = privilegeLevel; + } + + /** + * Checks if this player account has membership. + * @return {@code true} if so, {@code false} if not. + */ + public boolean isMembers() { + return members; + } + + /** + * Changes the membership status of this player. + * @param members The new membership flag. + */ + public void setMembers(boolean members) { + this.members = members; + } + + /** + * Sets the player's {@link GameSession}. + * @param session The player's {@link GameSession}. + * @param reconnecting The reconnecting flag. + */ + public void setSession(GameSession session, boolean reconnecting) { + this.session = session; + if (!reconnecting) { + sendInitialEvents(); + } + getBlockSet().add(SynchronizationBlock.createAppearanceBlock(this)); + } + + /** + * Gets the player's credentials. + * @return The player's credentials. + */ + public PlayerCredentials getCredentials() { + return credentials; + } + + @Override + public void send(Event event) { + if (isActive()) { + if (!queuedEvents.isEmpty()) { + for (Event queuedEvent : queuedEvents) { + session.dispatchEvent(queuedEvent); + } + queuedEvents.clear(); + } + session.dispatchEvent(event); + } else { + queuedEvents.add(event); + } + } + + /** + * Initialises this player. + */ + private void init() { + initInventories(); + initSkills(); + } + + /** + * Initialises the player's skills. + */ + private void initSkills() { + SkillSet skills = getSkillSet(); + + // synchronization listener + SkillListener syncListener = new SynchronizationSkillListener(this); + + // level up listener + SkillListener levelUpListener = new LevelUpSkillListener(this); + + // add the listeners + skills.addListener(syncListener); + skills.addListener(levelUpListener); + } + + /** + * Initialises the player's inventories. + */ + private void initInventories() { + Inventory inventory = getInventory(); + Inventory bank = getBank(); + Inventory equipment = getEquipment(); + + // TODO only add bank listener when it is open? (like Hyperion) + + // inventory full listeners + InventoryListener fullInventoryListener = new FullInventoryListener(this, FullInventoryListener.FULL_INVENTORY_MESSAGE); + InventoryListener fullBankListener = new FullInventoryListener(this, FullInventoryListener.FULL_BANK_MESSAGE); + InventoryListener fullEquipmentListener = new FullInventoryListener(this, FullInventoryListener.FULL_EQUIPMENT_MESSAGE); + + // equipment appearance listener + InventoryListener appearanceListener = new AppearanceInventoryListener(this); + + // synchronization listeners + InventoryListener syncInventoryListener = new SynchronizationInventoryListener(this, SynchronizationInventoryListener.INVENTORY_ID); + InventoryListener syncBankListener = new SynchronizationInventoryListener(this, BankConstants.BANK_INVENTORY_ID); + InventoryListener syncEquipmentListener = new SynchronizationInventoryListener(this, SynchronizationInventoryListener.EQUIPMENT_ID); + + // add the listeners + inventory.addListener(syncInventoryListener); + inventory.addListener(fullInventoryListener); + bank.addListener(syncBankListener); + bank.addListener(fullBankListener); + equipment.addListener(syncEquipmentListener); + equipment.addListener(appearanceListener); + equipment.addListener(fullEquipmentListener); + } + + /** + * Sends the initial events. + */ + private void sendInitialEvents() { + // vital initial stuff + send(new IdAssignmentEvent(getIndex(), members)); // TODO should this be sent when we reconnect? + sendMessage("Welcome to RuneScape."); + + // character design screen + if (!designedCharacter) { + interfaceSet.openWindow(3559); // TODO make the interface id a constant or something? + } + + // tabs TODO make a constant? look at player settings + int[] tabs = { + // 6299 = music tab, music disabled + // 4445 = settings tab, music disabled + // 12855 = ancients magic + 2423, 3917, 638, 3213, 1644, 5608, 1151, -1, 5065, 5715, 2449, 904, 147, 962, + }; + for (int i = 0; i < tabs.length; i++) { + send(new SwitchTabInterfaceEvent(i, tabs[i])); + } + + // force inventories to update + getInventory().forceRefresh(); + getEquipment().forceRefresh(); + getBank().forceRefresh(); + + // force skills to update + getSkillSet().forceRefresh(); + } + + @Override + public String toString() { + return Player.class.getName() + " [username=" + credentials.getUsername() + ", privilegeLevel=" + privilegeLevel +"]"; + } + + /** + * Sets the region changed flag. + * @param regionChanged A flag indicating if the region has changed. + */ + public void setRegionChanged(boolean regionChanged) { + this.regionChanged = regionChanged; + } + + /** + * Checks if the region has changed. + * @return {@code true} if so, {@code false} if not. + */ + public boolean hasRegionChanged() { + return regionChanged; + } + + /** + * Gets the player's appearance. + * @return The appearance. + */ + public Appearance getAppearance() { + return appearance; + } + + /** + * Sets the player's appearance. + * @param appearance The new appearance. + */ + public void setAppearance(Appearance appearance) { + this.appearance = appearance; + this.getBlockSet().add(SynchronizationBlock.createAppearanceBlock(this)); + } + + /** + * Gets the player's name. + * @return The player's name. + */ + public String getName() { + return credentials.getUsername(); + } + + /** + * Gets the player's name, encoded as a long. + * @return The encoded player name. + */ + public long getEncodedName() { + return credentials.getEncodedUsername(); + } + + /** + * Logs the player out, if possible. + */ + public void logout() { + send(new LogoutEvent()); + } + + /** + * Gets the game session. + * @return The game session. + */ + public GameSession getSession() { + return session; + } + + /** + * Sets the character design flag. + * @param designedCharacter A flag indicating if the character has been + * designed. + */ + public void setDesignedCharacter(boolean designedCharacter) { + this.designedCharacter = designedCharacter; + } + + /** + * Checks if the player has designed their character. + * @return A flag indicating if the player has designed their character. + */ + public boolean hasDesignedCharacter() { + return designedCharacter; + } + + /** + * Gets the withdrawing notes flag. + * @return The flag. + */ + public boolean isWithdrawingNotes() { + return withdrawingNotes; + } + + /** + * Sets the withdrawing notes flag. + * @param withdrawingNotes The flag. + */ + public void setWithdrawingNotes(boolean withdrawingNotes) { + this.withdrawingNotes = withdrawingNotes; + } + + @Override + public void teleport(Position position) { + super.teleport(position); // TODO put this in the same place as Character#teleport and WalkEventHandler!! + interfaceSet.close(); // TODO: should this be done if size == 0? + } + +} diff --git a/src/org/apollo/game/model/Position.java b/src/org/apollo/game/model/Position.java new file mode 100644 index 000000000..78d1e7737 --- /dev/null +++ b/src/org/apollo/game/model/Position.java @@ -0,0 +1,220 @@ +package org.apollo.game.model; + +/** + * Represents a position in the world. + * @author Graham + */ +public final class Position { + + /** + * The number of height levels. + */ + public static final int HEIGHT_LEVELS = 4; + + /** + * The maximum distance players/NPCs can 'see'. + */ + public static final int MAX_DISTANCE = 15; + + /** + * The x coordinate. + */ + private final int x; + + /** + * The y coordinate. + */ + private final int y; + + /** + * The height level. + */ + private final int height; + + /** + * Creates a position at the default height. + * @param x The x coordinate. + * @param y The y coordinate. + */ + public Position(int x, int y) { + this(x, y, 0); + } + + /** + * Creates a position with the specified height. + * @param x The x coordinate. + * @param y The y coordinate. + * @param height The height. + */ + public Position(int x, int y, int height) { + if (height < 0 || height >= HEIGHT_LEVELS) { + throw new IllegalArgumentException("Height out of bounds"); + } + this.x = x; + this.y = y; + this.height = height; + } + + /** + * Gets the x coordinate. + * @return The x coordinate. + */ + public int getX() { + return x; + } + + /** + * Gets the y coordinate. + * @return The y coordinate. + */ + public int getY() { + return y; + } + + /** + * Gets the height level. + * @return The height level. + */ + public int getHeight() { + return height; + } + + /** + * Gets the x coordinate of the region. + * @return The region x coordinate. + */ + public int getTopLeftRegionX() { + return (x / 8) - 6; + } + + /** + * Gets the y coordinate of the region. + * @return The region y coordinate. + */ + public int getTopLeftRegionY() { + return (y / 8) - 6; + } + + /** + * Gets the x coordinate of the central region. + * @return The x coordinate of the central region. + */ + public int getCentralRegionX() { + return x / 8; + } + + /** + * Gets the y coordinate of the central region. + * @return The y coordinate of the central region. + */ + public int getCentralRegionY() { + return y / 8; + } + + /** + * Gets the x coordinate inside the region of this position. + * @return The local x coordinate. + */ + public int getLocalX() { + return getLocalX(this); + } + + /** + * Gets the y coordinate inside the region of this position. + * @return The local y coordinate. + */ + public int getLocalY() { + return getLocalY(this); + } + + /** + * Gets the local x coordinate inside the region of the {@code base} + * position. + * @param base The base position. + * @return The local x coordinate. + */ + public int getLocalX(Position base) { + return x - (base.getTopLeftRegionX() * 8); + } + + /** + * Gets the local y coordinate inside the region of the {@code base} + * position. + * @param base The base position. + * @return The local y coordinate. + */ + public int getLocalY(Position base) { + return y - (base.getTopLeftRegionY() * 8); + } + + @Override + public int hashCode() { + return ((height << 30) & 0xC0000000) | ((y << 15) & 0x3FFF8000) | (x & 0x7FFF); + } + + /** + * Gets the distance between this position and another position. Only X and + * Y are considered (i.e. 2 dimensions). + * @param other The other position. + * @return The distance. + */ + public int getDistance(Position other) { + int deltaX = x - other.x; + int deltaY = y - other.y; + // TODO will rounding up interfere with other stuff? + return (int) Math.ceil(Math.sqrt(deltaX * deltaX + deltaY * deltaY)); + } + + /** + * Gets the longest horizontal or vertical delta between the two positions. + * @param other The other position. + * @return The longest horizontal or vertical delta. + */ + public int getLongestDelta(Position other) { + int deltaX = x - other.x; + int deltaY = y - other.y; + return Math.max(deltaX, deltaY); + } + + /** + * Checks if the position is within distance of another. + * @param other The other position. + * @param distance The distance. + * @return {@code true} if so, {@code false} if not. + */ + public boolean isWithinDistance(Position other, int distance) { + int deltaX = Math.abs(x - other.x); + int deltaY = Math.abs(y - other.y); + return deltaX <= distance && deltaY <= distance; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Position other = (Position) obj; + if (height != other.height) { + return false; + } + if (x != other.x) { + return false; + } + if (y != other.y) { + return false; + } + return true; + } + + @Override + public String toString() { + return Position.class.getName() + " [x=" + x + ", y=" + y + ", height=" + height + "]"; + } + +} diff --git a/src/org/apollo/game/model/Skill.java b/src/org/apollo/game/model/Skill.java new file mode 100644 index 000000000..e95780d5b --- /dev/null +++ b/src/org/apollo/game/model/Skill.java @@ -0,0 +1,181 @@ +package org.apollo.game.model; + +/** + * Represents a single skill. + * @author Graham + */ +public final class Skill { + + /** + * The attack id. + */ + public static final int ATTACK = 0; + + /** + * The defence id. + */ + public static final int DEFENCE = 1; + + /** + * The strength id. + */ + public static final int STRENGTH = 2; + + /** + * The hitpoints id. + */ + public static final int HITPOINTS = 3; + + /** + * The ranged id. + */ + public static final int RANGED = 4; + + /** + * The prayer id. + */ + public static final int PRAYER = 5; + + /** + * The magic id. + */ + public static final int MAGIC = 6; + + /** + * The cooking id. + */ + public static final int COOKING = 7; + + /** + * The woodcutting id. + */ + public static final int WOODCUTTING = 8; + + /** + * The fletching id. + */ + public static final int FLETCHING = 9; + + /** + * The fishing id. + */ + public static final int FISHING = 10; + + /** + * The firemaking id. + */ + public static final int FIREMAKING = 11; + + /** + * The crafting id. + */ + public static final int CRAFTING = 12; + + /** + * The smithing id. + */ + public static final int SMITHING = 13; + + /** + * The mining id.rivate + */ + public static final int MINING = 14; + + /** + * The herblore id. + */ + public static final int HERBLORE = 15; + + /** + * The agility id. + */ + public static final int AGILITY = 16; + + /** + * The thieving id. + */ + public static final int THIEVING = 17; + + /** + * The slayer id. + */ + public static final int SLAYER = 18; + + /** + * The farming id. + */ + public static final int FARMING = 19; + + /** + * The runecrafting id. + */ + public static final int RUNECRAFTING = 20; + + /** + * The skill names. + */ + private static final String SKILL_NAMES[] = { "Attack", "Defence", "Strength", "Hitpoints", "Ranged", "Prayer", + "Magic", "Cooking", "Woodcutting", "Fletching", "Fishing", "Firemaking", "Crafting", "Smithing", "Mining", + "Herblore", "Agility", "Thieving", "Slayer", "Farming", "Runecraft" }; + + /** + * Gets the name of a skill. + * @param id The skill's id. + * @return The skill's name. + */ + public static String getName(int id) { + return SKILL_NAMES[id]; + } + + /** + * The experience. + */ + private final double experience; + + /** + * The current level. + */ + private final int currentLevel; + + /** + * The maximum level. + */ + private final int maximumLevel; + + /** + * Creates a skill. + * @param experience The experience. + * @param currentLevel The current level. + * @param maximumLevel The maximum level. + */ + public Skill(double experience, int currentLevel, int maximumLevel) { + this.experience = experience; + this.currentLevel = currentLevel; + this.maximumLevel = maximumLevel; + } + + /** + * Gets the experience. + * @return The experience. + */ + public double getExperience() { + return experience; + } + + /** + * Gets the current level. + * @return The current level. + */ + public int getCurrentLevel() { + return currentLevel; + } + + /** + * Gets the maximum level. + * @return The maximum level. + */ + public int getMaximumLevel() { + return maximumLevel; + } + +} diff --git a/src/org/apollo/game/model/SkillSet.java b/src/org/apollo/game/model/SkillSet.java new file mode 100644 index 000000000..e3063eab9 --- /dev/null +++ b/src/org/apollo/game/model/SkillSet.java @@ -0,0 +1,316 @@ +package org.apollo.game.model; + +import java.util.ArrayList; +import java.util.List; + +import org.apollo.game.model.skill.SkillListener; + +/** + * Represents the set of the player's skills. + * @author Graham + */ +public final class SkillSet { + + /** + * The number of skills. + */ + private static final int SKILL_COUNT = 21; + + /** + * The maximum allowed experience. + */ + public static final double MAXIMUM_EXP = 200000000; + + /** + * A list of skill listeners. + */ + private final List listeners = new ArrayList(); + + /** + * The skills. + */ + private final Skill[] skills = new Skill[SKILL_COUNT]; + + /** + * A flag indicating if events are being fired. + */ + private boolean firingEvents = true; + + /** + * Creates the skill set. + */ + public SkillSet() { + init(); + } + + /** + * Gets the number of skills. + * @return The number of skills. + */ + public int size() { + return skills.length; + } + + /** + * Initialises the skill set. + */ + private void init() { + for (int i = 0; i < skills.length; i++) { + if (i == Skill.HITPOINTS) { + skills[i] = new Skill(1154, 10, 10); + } else { + skills[i] = new Skill(0, 1, 1); + } + // DO NOT CALL notifyXXX here!! + } + } + + /** + * Gets a skill by its id. + * @param id The id. + * @return The skill. + * @throws IndexOutOfBoundsException if the id is out of bounds. + */ + public Skill getSkill(int id) { + checkBounds(id); + return skills[id]; + } + + /** + * Adds experience to the specified skill. + * @param id The skill id. + * @param experience The amount of experience. + */ + public void addExperience(int id, double experience) { + checkBounds(id); + + Skill old = skills[id]; + + double newExperience = old.getExperience() + experience; + + if (newExperience > MAXIMUM_EXP) { + newExperience = MAXIMUM_EXP; + } + + int newCurrentLevel = old.getCurrentLevel(); + int newMaximumLevel = getLevelForExperience(newExperience); + + int delta = newMaximumLevel - old.getMaximumLevel(); + if (delta > 0) { + newCurrentLevel += delta; + } + + setSkill(id, new Skill(newExperience, newCurrentLevel, newMaximumLevel)); + + if (delta > 0) { + // here so it gets updated skill + notifyLevelledUp(id); + } + } + + /** + * Gets the total level for this skill set. + * @return The total level. + */ + public int getTotalLevel() { + int total = 0; + for(int i = 0; i < skills.length; i++) { + total += skills[i].getMaximumLevel(); + } + return total; + } + + /** + * Gets the combat level for this skill set. + * @return The combat level. + */ + public int getCombatLevel() { + int attack = skills[Skill.ATTACK].getMaximumLevel(); + int defence = skills[Skill.DEFENCE].getMaximumLevel(); + int strength = skills[Skill.STRENGTH].getMaximumLevel(); + int hitpoints = skills[Skill.HITPOINTS].getMaximumLevel(); + int prayer = skills[Skill.PRAYER].getMaximumLevel(); + int ranged = skills[Skill.RANGED].getMaximumLevel(); + int magic = skills[Skill.MAGIC].getMaximumLevel(); + + int combat = (int) (((defence + hitpoints + prayer / 2) * 0.235) + 1); + + double melee = (attack + strength) * 0.325; + double ranger = Math.floor(ranged * 1.5) * 0.325; + double mage = Math.floor(magic * 1.5) * 0.325; + + if (melee >= ranger && melee >= mage) { + combat += melee; + } else if (ranger >= melee && ranger >= mage) { + combat += ranger; + } else if (mage >= melee && mage >= ranger) { + combat += mage; + } + + return combat < 126 ? combat : 126; + } + + /** + * Gets the minimum experience required for the specified level. + * @param level The level. + * @return The minimum experience. + */ + public static double getExperienceForLevel(int level) { + int points = 0; + int output = 0; + for (int lvl = 1; lvl <= level; lvl++) { + points += Math.floor(lvl + 300.0 * Math.pow(2.0, lvl / 7.0)); + if (lvl >= level) { + return output; + } + output = (int) Math.floor(points / 4); + } + return 0; + } + + /** + * Gets the minimum level to get the specified experience. + * @param experience The experience. + * @return The minimum level. + */ + public static int getLevelForExperience(double experience) { + int points = 0; + int output = 0; + for (int lvl = 1; lvl <= 99; lvl++) { + points += Math.floor(lvl + 300.0 * Math.pow(2.0, lvl / 7.0)); + output = (int) Math.floor(points / 4); + if (output >= (experience + 1)) { + return lvl; + } + } + return 99; + } + + /** + * Normalizes the skills in this set. + */ + public void normalize() { + for (int i = 0; i < skills.length; i++) { + // TODO I think prayer works differently(?) + int cur = skills[i].getCurrentLevel(); + int max = skills[i].getMaximumLevel(); + + if (cur > max) { + cur--; + } else if (max > cur) { + cur++; + } else { + continue; + } + + setSkill(i, new Skill(skills[i].getExperience(), cur, max)); + } + } + + /** + * Sets a skill. + * @param id The id. + * @param skill The skill. + * @throws IndexOutOfBoundsException if the id is out of bounds. + */ + public void setSkill(int id, Skill skill) { + checkBounds(id); + skills[id] = skill; + notifySkillUpdated(id); + } + + /** + * Checks the bounds of the id. + * @param id The id. + * @throws IndexOutOfBoundsException if the id is out of bounds. + */ + private void checkBounds(int id) { + if (id < 0 || id >= skills.length) { + throw new IndexOutOfBoundsException(); + } + } + + /** + * Notifies listeners that a skill has been levelled up. + * @param id The skill's id. + * @throws IndexOutOfBoundsException if the id is out of bounds. + */ + private void notifyLevelledUp(int id) { + checkBounds(id); + if (firingEvents) { + for (SkillListener listener : listeners) { + listener.levelledUp(this, id, skills[id]); + } + } + } + + /** + * Notifies listeners that a skill has been updated. + * @param id The skill's id. + * @throws IndexOutOfBoundsException if the id is out of bounds. + */ + private void notifySkillUpdated(int id) { + checkBounds(id); + if (firingEvents) { + for (SkillListener listener : listeners) { + listener.skillUpdated(this, id, skills[id]); + } + } + } + + /** + * Notifies listeners that the skills in this listener have been updated. + */ + private void notifySkillsUpdated() { + if (firingEvents) { + for (SkillListener listener : listeners) { + listener.skillsUpdated(this); + } + } + } + + /** + * Stops events from being fired. + */ + public void stopFiringEvents() { + firingEvents = false; + } + + /** + * Re-enables the firing of events. + */ + public void startFiringEvents() { + firingEvents = true; + } + + /** + * Forces this skill set to refresh. + */ + public void forceRefresh() { + notifySkillsUpdated(); + } + + /** + * Adds a listener. + * @param listener The listener to add. + */ + public void addListener(SkillListener listener) { + listeners.add(listener); + } + + /** + * Removes a listener. + * @param listener The listener to remove. + */ + public void removeListener(SkillListener listener) { + listeners.remove(listener); + } + + /** + * Removes all the listeners. + */ + public void removeAllListeners() { + listeners.clear(); + } + +} diff --git a/src/org/apollo/game/model/SlottedItem.java b/src/org/apollo/game/model/SlottedItem.java new file mode 100644 index 000000000..cb59bcd10 --- /dev/null +++ b/src/org/apollo/game/model/SlottedItem.java @@ -0,0 +1,45 @@ +package org.apollo.game.model; + +/** + * A class which contains an {@link Item} and its corresponding slot. + * @author Graham + */ +public final class SlottedItem { + + /** + * The slot. + */ + private final int slot; + + /** + * The item. + */ + private final Item item; + + /** + * Creates a new slotted item. + * @param slot The slot. + * @param item The item. + */ + public SlottedItem(int slot, Item item) { + this.slot = slot; + this.item = item; + } + + /** + * Gets the slot. + * @return The slot. + */ + public int getSlot() { + return slot; + } + + /** + * Gets the item. + * @return The item. + */ + public Item getItem() { + return item; + } + +} diff --git a/src/org/apollo/game/model/WalkingQueue.java b/src/org/apollo/game/model/WalkingQueue.java new file mode 100644 index 000000000..f748dea94 --- /dev/null +++ b/src/org/apollo/game/model/WalkingQueue.java @@ -0,0 +1,248 @@ +package org.apollo.game.model; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.Queue; + +/** + * A queue of {@link Direction}s which a {@link Character} will follow. + * @author Graham + */ +public final class WalkingQueue { + + /** + * The maximum size of the queue. If any additional steps are added, they + * are discarded. + */ + private static final int MAXIMUM_SIZE = 128; + + /** + * Represents a single point in the queue. + * @author Graham + */ + private static final class Point { + + /** + * The point's position. + */ + private final Position position; + + /** + * The direction to walk to this point. + */ + private final Direction direction; + + /** + * Creates a point. + * @param position The position. + * @param direction The direction. + */ + public Point(Position position, Direction direction) { + this.position = position; + this.direction = direction; + } + + @Override + public String toString() { + return Point.class.getName() + " [direction=" + direction + ", position=" + position + "]"; + } + + } + + /** + * The character whose walking queue this is. + */ + private Character character; + + /** + * The queue of directions. + */ + private Deque points = new ArrayDeque(); + + /** + * The old queue of directions. + */ + private Deque oldPoints = new ArrayDeque(); + + /** + * Flag indicating if this queue (only) should be ran. + */ + private boolean runningQueue; + + /** + * Creates a walking queue for the specified character. + * @param character The character. + */ + public WalkingQueue(Character character) { + this.character = character; + } + + /** + * Called every pulse, updates the queue. + */ + public void pulse() { + Position position = character.getPosition(); + + Direction first = Direction.NONE; + Direction second = Direction.NONE; + + Point next = points.poll(); + if (next != null) { + first = next.direction; + position = next.position; + + if (runningQueue /* or run toggled AND enough energy */) { + next = points.poll(); + if (next != null) { + second = next.direction; + position = next.position; + } + } + } + + character.setDirections(first, second); + character.setPosition(position); + } + + /** + * Sets the running queue flag. + * @param running The running queue flag. + */ + public void setRunningQueue(boolean running) { + this.runningQueue = running; + } + + /** + * Adds the first step to the queue, attempting to connect the server and + * client position by looking at the previous queue. + * @param clientConnectionPosition The first step. + * @return {@code true} if the queues could be connected correctly, + * {@code false} if not. + */ + public boolean addFirstStep(Position clientConnectionPosition) { + Position serverPosition = character.getPosition(); + + int deltaX = clientConnectionPosition.getX() - serverPosition.getX(); + int deltaY = clientConnectionPosition.getY() - serverPosition.getY(); + + if (Direction.isConnectable(deltaX, deltaY)) { + points.clear(); + oldPoints.clear(); + + addStep(clientConnectionPosition); + return true; + } + + Queue travelBackQueue = new ArrayDeque(); + + Point oldPoint; + while ((oldPoint = oldPoints.pollLast()) != null) { + Position oldPosition = oldPoint.position; + + deltaX = oldPosition.getX() - serverPosition.getX(); + deltaY = oldPosition.getX() - serverPosition.getY(); + + travelBackQueue.add(oldPosition); + + if (Direction.isConnectable(deltaX, deltaY)) { + points.clear(); + oldPoints.clear(); + + for (Position travelBackPosition : travelBackQueue) { + addStep(travelBackPosition); + } + + addStep(clientConnectionPosition); + return true; + } + } + + oldPoints.clear(); + return false; + } + + /** + * Adds a step to the queue. + * @param step The step to add. + */ + public void addStep(Position step) { + Point last = getLast(); + + int x = step.getX(); + int y = step.getY(); + + int deltaX = x - last.position.getX(); + int deltaY = y - last.position.getY(); + + int max = Math.max(Math.abs(deltaX), Math.abs(deltaY)); + + for (int i = 0; i < max; i++) { + if (deltaX < 0) { + deltaX++; + } else if (deltaX > 0) { + deltaX--; + } + + if (deltaY < 0) { + deltaY++; + } else if (deltaY > 0) { + deltaY--; + } + + addStep(x - deltaX, y - deltaY); + } + } + + /** + * Adds a step. + * @param x The x coordinate of this step. + * @param y The y coordinate of this step. + */ + private void addStep(int x, int y) { + if (points.size() >= MAXIMUM_SIZE) { + return; + } + + Point last = getLast(); + + int deltaX = x - last.position.getX(); + int deltaY = y - last.position.getY(); + + Direction direction = Direction.fromDeltas(deltaX, deltaY); + + if (direction != Direction.NONE) { + Point p = new Point(new Position(x, y), direction); + points.add(p); + oldPoints.add(p); + } + } + + /** + * Gets the last point. + * @return The last point. + */ + private Point getLast() { + Point last = points.peekLast(); + if (last == null) { + return new Point(character.getPosition(), Direction.NONE); + } + return last; + } + + /** + * Clears the walking queue. + */ + public void clear() { + points.clear(); + oldPoints.clear(); + } + + /** + * Gets the size of the queue. + * @return The size of the queue. + */ + public int size() { + return points.size(); + } + +} diff --git a/src/org/apollo/game/model/World.java b/src/org/apollo/game/model/World.java new file mode 100644 index 000000000..496a28a2f --- /dev/null +++ b/src/org/apollo/game/model/World.java @@ -0,0 +1,227 @@ +package org.apollo.game.model; + +import java.io.BufferedInputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.logging.Logger; + +import org.apollo.Service; +import org.apollo.fs.IndexedFileSystem; +import org.apollo.fs.parser.ItemDefinitionParser; +import org.apollo.game.command.CommandDispatcher; +import org.apollo.game.model.def.EquipmentDefinition; +import org.apollo.game.model.def.ItemDefinition; +import org.apollo.game.scheduling.ScheduledTask; +import org.apollo.game.scheduling.Scheduler; +import org.apollo.io.EquipmentDefinitionParser; +import org.apollo.util.CharacterRepository; +import org.apollo.util.plugin.PluginManager; + +/** + * The world class is a singleton which contains objects like the + * {@link CharacterRepository} for players and NPCs. It should only contain + * things relevant to the in-game world and not classes which deal with I/O and + * such (these may be better off inside some custom {@link Service} or other + * code, however, the circumstances are rare). + * @author Graham + */ +public final class World { + + /** + * The logger for this class. + */ + private static final Logger logger = Logger.getLogger(World.class.getName()); + + /** + * The world. + */ + private static final World world = new World(); + + /** + * Represents the different status codes for registering a player. + * @author Graham + */ + public enum RegistrationStatus { + + /** + * Indicates the world is full. + */ + WORLD_FULL, + + /** + * Indicates that the player is already online. + */ + ALREADY_ONLINE, + + /** + * Indicates that the player was registered successfully. + */ + OK; + } + + /** + * Gets the world. + * @return The world. + */ + public static World getWorld() { + return world; + } + + /** + * The scheduler. + */ + // TODO: better place than here? + private final Scheduler scheduler = new Scheduler(); + + /** + * The command dispatcher. + */ + // TODO: better place than here? + private final CommandDispatcher dispatcher = new CommandDispatcher(); + + // TODO: better place than here!! + private PluginManager pluginManager; + + /** + * The {@link CharacterRepository} of {@link Player}s. + */ + private final CharacterRepository playerRepository = new CharacterRepository(WorldConstants.MAXIMUM_PLAYERS); + + /** + * Creates the world. + */ + private World() { + + } + + /** + * Initialises the world by loading definitions from the specified file + * system. + * @param release The release number. + * @param fs The file system. + * @param mgr 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)."); + + 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); + } finally { + is.close(); + } + logger.info("Done (loaded " + nonNull + " equipment definitions)."); + + this.pluginManager = mgr; // TODO move!! + } + + /** + * Gets the character repository. NOTE: + * {@link CharacterRepository#add(Character)} and + * {@link CharacterRepository#remove(Character)} should not be called + * directly! These mutation methods are not guaranteed to work in future + * releases! + *

+ * Instead, use the {@link World#register(Player)} and + * {@link World#unregister(Player)} methods which do the same thing and + * will continue to work as normal in future releases. + * @return The character repository. + */ + public CharacterRepository getPlayerRepository() { + return playerRepository; + } + + /** + * Registers the specified player. + * @param player The player. + * @return A {@link RegistrationStatus}. + */ + public RegistrationStatus register(final Player player) { + if (isPlayerOnline(player.getName())) { + return RegistrationStatus.ALREADY_ONLINE; + } + + boolean success = playerRepository.add(player); + if (success) { + logger.info("Registered player: " + player + " [online=" + playerRepository.size() + "]"); + return RegistrationStatus.OK; + } else { + logger.warning("Failed to register player (server full): " + player + " [online=" + playerRepository.size() + "]"); + return RegistrationStatus.WORLD_FULL; + } + } + + /** + * Checks if the specified player is online. + * @param name The player's name. + * @return {@code true} if so, {@code false} if not. + */ + public boolean isPlayerOnline(String name) { + // TODO: use a hash set or map in the future? + for (Player player : playerRepository) { + if (player.getName().equalsIgnoreCase(name)) { + return true; + } + } + return false; + } + + /** + * Unregisters the specified player. + * @param player The player. + */ + public void unregister(Player player) { + if (playerRepository.remove(player)) { + logger.info("Unregistered player: " + player + " [online=" + playerRepository.size() + "]"); + } else { + logger.warning("Could not find player to unregister: " + player + "!"); + } + } + + /** + * Schedules a new task. + * @param task The {@link ScheduledTask}. + */ + public void schedule(ScheduledTask task) { + scheduler.schedule(task); + } + + /** + * Calls the {@link Scheduler#pulse()} method. + */ + public void pulse() { + scheduler.pulse(); + } + + /** + * Gets the command dispatcher. TODO should this be here? + * @return The command dispatcher. + */ + public CommandDispatcher getCommandDispatcher() { + return dispatcher; + } + + /** + * Gets the plugin manager. TODO should this be here? + * @return The plugin manager. + */ + public PluginManager getPluginManager() { + return pluginManager; + } + +} diff --git a/src/org/apollo/game/model/WorldConstants.java b/src/org/apollo/game/model/WorldConstants.java new file mode 100644 index 000000000..079c73e8f --- /dev/null +++ b/src/org/apollo/game/model/WorldConstants.java @@ -0,0 +1,21 @@ +package org.apollo.game.model; + +/** + * Holds world-related constants. + * @author Graham + */ +public final class WorldConstants { + + /** + * The maximum number of players. + */ + public static final int MAXIMUM_PLAYERS = 2000; + + /** + * Default private constructor to prevent instantiation by other classes. + */ + private WorldConstants() { + + } + +} diff --git a/src/org/apollo/game/model/def/EquipmentDefinition.java b/src/org/apollo/game/model/def/EquipmentDefinition.java new file mode 100644 index 000000000..e4771d208 --- /dev/null +++ b/src/org/apollo/game/model/def/EquipmentDefinition.java @@ -0,0 +1,203 @@ +package org.apollo.game.model.def; + +import java.util.HashMap; +import java.util.Map; + +import org.apollo.game.model.Item; + +/** + * Represents a type of {@link Item} which may be equipped. + * @author Graham + */ +public final class EquipmentDefinition { + + /** + * The equipment definitions. + */ + private static final Map definitions = new HashMap(); + + /** + * Initialises the equipment definitions. + * @param definitions The definitions. + */ + public static void init(EquipmentDefinition[] definitions) { + for (int id = 0; id < definitions.length; id++) { + EquipmentDefinition def = definitions[id]; + if (def != null) { + if (def.getId() != id) { + throw new RuntimeException("Item definition id mismatch!"); + } + EquipmentDefinition.definitions.put(def.getId(), def); + } + } + } + + /** + * Gets an equipment definition by its id. + * @param id The id. + * @return {@code null} if the item is not equipment, the definition + * otherwise. + * @throws IndexOutOfBoundsException if the id is out of bounds. + */ + public static EquipmentDefinition forId(int id) { + if (id < 0 || id >= ItemDefinition.count()) { + throw new IndexOutOfBoundsException(); + } + return definitions.get(id); + } + + /** + * The item id. + */ + private final int id; + + /** + * The slot this equipment goes into. + */ + private int slot; + + /** + * The required levels. + */ + private int attack = 1, strength = 1, defence = 1, ranged = 1, magic = 1; + + /** + * Various flags. + */ + private boolean twoHanded, fullBody, fullHat, fullMask; + + /** + * Creates a new equipment definition. + * @param id The id. + */ + public EquipmentDefinition(int id) { + this.id = id; + } + + /** + * Gets the id. + * @return The id. + */ + public int getId() { + return id; + } + + /** + * Sets the required levels. + * @param attack The required attack level. + * @param strength The required strength level. + * @param defence The required defence level. + * @param ranged The required ranged level. + * @param magic The required magic level. + */ + public void setLevels(int attack, int strength, int defence, int ranged, int magic) { + this.attack = attack; + this.strength = strength; + this.defence = defence; + this.ranged = ranged; + this.magic = magic; + } + + /** + * Gets the minimum attack level. + * @return The minimum attack level. + */ + public int getAttackLevel() { + return attack; + } + + /** + * Gets the minimum strength level. + * @return The minimum strength level. + */ + public int getStrengthLevel() { + return strength; + } + + /** + * Gets the minimum defence level. + * @return The minimum defence level. + */ + public int getDefenceLevel() { + return defence; + } + + /** + * Gets the minimum ranged level. + * @return The minimum ranged level. + */ + public int getRangedLevel() { + return ranged; + } + + /** + * Gets the minimum magic level. + * @return The minimum magic level. + */ + public int getMagicLevel() { + return magic; + } + + /** + * Sets the target slot. + * @param slot The target slot. + */ + public void setSlot(int slot) { + this.slot = slot; + } + + /** + * Gets the target slot. + * @return The target slot. + */ + public int getSlot() { + return slot; + } + + /** + * Checks if this equipment is two-handed. + * @return {@code true} if so, {@code false} if not. + */ + public boolean isTwoHanded() { + return twoHanded; + } + + /** + * Checks if this equipment is a full body. + * @return {@code true} if so, {@code false} if not. + */ + public boolean isFullBody() { + return fullBody; + } + + /** + * Checks if this equipment is a full hat. + * @return {@code true} if so, {@code false} if not. + */ + public boolean isFullHat() { + return fullHat; + } + + /** + * Checks if this equipment is a full mask. + * @return {@code true} if so, {@code false} if not. + */ + public boolean isFullMask() { + return fullMask; + } + + /** + * Sets the flags. + * @param twoHanded The two handed flag. + * @param fullBody The full body flag. + * @param fullHat The full hat flag. + * @param fullMask The full mask flag. + */ + public void setFlags(boolean twoHanded, boolean fullBody, boolean fullHat, boolean fullMask) { + this.twoHanded = twoHanded; + this.fullBody = fullBody; + this.fullHat = fullHat; + this.fullMask = fullMask; + } + +} diff --git a/src/org/apollo/game/model/def/ItemDefinition.java b/src/org/apollo/game/model/def/ItemDefinition.java new file mode 100644 index 000000000..6d3e12e66 --- /dev/null +++ b/src/org/apollo/game/model/def/ItemDefinition.java @@ -0,0 +1,363 @@ +package org.apollo.game.model.def; + +import org.apollo.game.model.Item; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; + +/** + * Represents a type of {@link Item}. + * @author Graham + */ +public final class ItemDefinition { + + /** + * The item definitions. + */ + private static ItemDefinition[] definitions; + + /** + * A map of item ids to noted ids. + */ + private static final BiMap notes = HashBiMap.create(); + + /** + * A map of noted ids to item ids. + */ + private static final BiMap notesInverse = notes.inverse(); + + /** + * Converts an item id to a noted id. + * @param id The item id. + * @return The noted id. + */ + public static int itemToNote(int id) { + Integer entry = notes.get(id); + if (entry == null) { + return id; + } + return entry; + } + + /** + * Converts a noted id to the normal item id. + * @param id The note id. + * @return The item id. + */ + public static int noteToItem(int id) { + Integer entry = notesInverse.get(id); + if (entry == null) { + return id; + } + return entry; + } + + /** + * 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(ItemDefinition[] definitions) { + ItemDefinition.definitions = definitions; + for (int id = 0; id < definitions.length; id++) { + ItemDefinition def = definitions[id]; + if (def.getId() != id) { + throw new RuntimeException("Item definition id mismatch!"); + } + if (def.isNote()) { + def.toNote(); + notes.put(def.getNoteInfoId(), def.getId()); + } + } + } + + /** + * Gets the total number of items. + * @return The total number of items. + */ + public static int count() { + return definitions.length; + } + + /** + * Gets the item definition for the specified id. + * @param id The id. + * @return The definition. + * @throws IndexOutOfBoundsException if the id is out of bounds. + */ + public static ItemDefinition forId(int id) { + if (id < 0 || id >= definitions.length) { + throw new IndexOutOfBoundsException(); + } + return definitions[id]; + } + + /** + * The item's id. + */ + private final int id; + + /** + * The name of the item. + */ + private String name; + + /** + * The description of the item. + */ + private String description; + + /** + * A flag indicating if this item is stackable. + */ + private boolean stackable; + + /** + * The value of the item. + */ + private int value; + + /** + * A flag indicating if this item is members only. + */ + private boolean members; + + /** + * The ground actions array. + */ + private String[] groundActions = new String[5]; + + /** + * The inventory actions array. + */ + private String[] inventoryActions = new String[5]; + + /** + * The id of the item to copy note info from. + */ + private int noteInfoId = -1; + + /** + * The id of the item to copy note graphics from. + */ + private int noteGraphicId = -1; + + /** + * Creates an item definition with the default values. + * @param id The item's id. + */ + public ItemDefinition(int id) { + this.id = id; + } + + /** + * Gets the note info id. + * @return The note info id. + */ + public int getNoteInfoId() { + return noteInfoId; + } + + /** + * Gets the note graphic id. + * @return The note graphic id. + */ + public int getNoteGraphicId() { + return noteGraphicId; + } + + /** + * Gets the item's id. + * @return The item's id. + */ + public int getId() { + return id; + } + + /** + * Sets the note info id. + * @param noteInfoId The note info id. + */ + public void setNoteInfoId(int noteInfoId) { + this.noteInfoId = noteInfoId; + } + + /** + * Sets the note graphic id. + * @param noteGraphicId The note graphic id. + */ + public void setNoteGraphicId(int noteGraphicId) { + this.noteGraphicId = noteGraphicId; + } + + /** + * Checks if this item is a note. + * @return {@code true} if so, {@code false} otherwise. + */ + public boolean isNote() { + return noteGraphicId != -1 && noteInfoId != -1; + } + + /** + * Converts this item to a note, if possible. + * @throws IllegalStateException if {@link ItemDefinition#isNote()} returns + * {@code false}. + */ + public void toNote() { + if (isNote()) { + if (description != null && description.startsWith("Swap this note at any bank for ")) { + return; // already converted TODO better way of checking? + } + + ItemDefinition infoDef = forId(noteInfoId); + name = infoDef.name; + members = infoDef.members; + value = infoDef.value; + + String prefix = "a"; + char firstChar = name == null ? 'n' : name.charAt(0); + + if (firstChar == 'A' || firstChar == 'E' || firstChar == 'I' || firstChar == 'O' || firstChar == 'U') { + prefix = "an"; + } + + description = "Swap this note at any bank for " + prefix + " " + name + "."; + stackable = true; + } else { + throw new IllegalStateException(); + } + } + + /** + * Sets the name of this item. + * @param name The item's name. + */ + public void setName(String name) { + this.name = name; + } + + /** + * Gets the name of this item. + * @return The name of this item, or {@code null} if it has no name. + */ + public String getName() { + return name; + } + + /** + * Sets the description of this item. + * @param description The item's description. + */ + public void setDescription(String description) { + this.description = description; + } + + /** + * Gets the description of this item. + * @return The item's description. + */ + public String getDescription() { + return description; + } + + /** + * Sets the stackable flag. + * @param stackable The stackable flag. + */ + public void setStackable(boolean stackable) { + this.stackable = stackable; + } + + /** + * Checks if the item specified by this definition is stackable. + * @return {@code true} if so, {@code false} if not. + */ + public boolean isStackable() { + return stackable; + } + + /** + * Sets the value of this item. + * @param value The value of this item. + */ + public void setValue(int value) { + this.value = value; + } + + /** + * Gets the value of this item. + * @return The value of this item. + */ + public int getValue() { + return value; + } + + /** + * Sets the members only flag. + * @param members The members only flag. + */ + public void setMembersOnly(boolean members) { + this.members = members; + } + + /** + * Checks if this item is members only. + * @return {@code true} if so, {@code false} if not. + */ + public boolean isMembersOnly() { + return members; + } + + /** + * Sets a ground action. + * @param id The id. + * @param action The action. + * @throws IndexOutOfBoundsException if the id is out of bounds. + */ + public void setGroundAction(int id, String action) { + if (id < 0 || id >= groundActions.length) { + throw new IndexOutOfBoundsException(); + } + groundActions[id] = action; + } + + /** + * Gets a ground action. + * @param id The id. + * @return The action. + * @throws IndexOutOfBoundsException if the id is out of bounds. + */ + public String getGroundAction(int id) { + if (id < 0 || id >= groundActions.length) { + throw new IndexOutOfBoundsException(); + } + return groundActions[id]; + } + + /** + * Sets an inventory action. + * @param id The id. + * @param action The action. + * @throws IndexOutOfBoundsException if the id is out of bounds. + */ + public void setInventoryAction(int id, String action) { + if (id < 0 || id >= inventoryActions.length) { + throw new IndexOutOfBoundsException(); + } + inventoryActions[id] = action; + } + + /** + * Gets an inventory action. + * @param id The id. + * @return The action. + * @throws IndexOutOfBoundsException if the id is out of bounds. + */ + public String getInventoryAction(int id) { + if (id < 0 || id >= inventoryActions.length) { + throw new IndexOutOfBoundsException(); + } + return inventoryActions[id]; + } + +} diff --git a/src/org/apollo/game/model/def/StaticObjectDefinition.java b/src/org/apollo/game/model/def/StaticObjectDefinition.java new file mode 100644 index 000000000..d45bc0edd --- /dev/null +++ b/src/org/apollo/game/model/def/StaticObjectDefinition.java @@ -0,0 +1,11 @@ +package org.apollo.game.model.def; + +import org.apollo.game.model.obj.StaticObject; + +/** + * Represents a type of {@link StaticObject}. + * @author Graham + */ +public final class StaticObjectDefinition { + +} diff --git a/src/org/apollo/game/model/def/package-info.java b/src/org/apollo/game/model/def/package-info.java new file mode 100644 index 000000000..70a6c2b35 --- /dev/null +++ b/src/org/apollo/game/model/def/package-info.java @@ -0,0 +1,5 @@ +/** + * Contains definition classes which contain information about types of items, + * NPCs, etc. + */ +package org.apollo.game.model.def; diff --git a/src/org/apollo/game/model/inter/EnterAmountListener.java b/src/org/apollo/game/model/inter/EnterAmountListener.java new file mode 100644 index 000000000..2e7d8b087 --- /dev/null +++ b/src/org/apollo/game/model/inter/EnterAmountListener.java @@ -0,0 +1,15 @@ +package org.apollo.game.model.inter; + +/** + * A listener for the enter amount dialog. + * @author Graham + */ +public interface EnterAmountListener { + + /** + * Called when the player enters the specified amount. + * @param amount The amount. + */ + public void amountEntered(int amount); + +} diff --git a/src/org/apollo/game/model/inter/InterfaceListener.java b/src/org/apollo/game/model/inter/InterfaceListener.java new file mode 100644 index 000000000..71d3e77c5 --- /dev/null +++ b/src/org/apollo/game/model/inter/InterfaceListener.java @@ -0,0 +1,14 @@ +package org.apollo.game.model.inter; + +/** + * Listens to interface-related events. + * @author Graham + */ +public interface InterfaceListener { + + /** + * Called when the interface has been closed. + */ + public void interfaceClosed(); + +} diff --git a/src/org/apollo/game/model/inter/bank/BankConstants.java b/src/org/apollo/game/model/inter/bank/BankConstants.java new file mode 100644 index 000000000..6e2748eca --- /dev/null +++ b/src/org/apollo/game/model/inter/bank/BankConstants.java @@ -0,0 +1,36 @@ +package org.apollo.game.model.inter.bank; + +/** + * Contains bank-related constants. + * @author Graham + */ +public final class BankConstants { + + /** + * The bank window id. + */ + public static final int BANK_WINDOW_ID = 5292; + + /** + * The sidebar id. + */ + public static final int SIDEBAR_ID = 2005; + + /** + * The bank inventory id. + */ + public static final int BANK_INVENTORY_ID = 5382; + + /** + * The sidebar inventory id. + */ + public static final int SIDEBAR_INVENTORY_ID = 2006; + + /** + * Default private constructor to prevent instantiation. + */ + private BankConstants() { + + } + +} diff --git a/src/org/apollo/game/model/inter/bank/BankDepositEnterAmountListener.java b/src/org/apollo/game/model/inter/bank/BankDepositEnterAmountListener.java new file mode 100644 index 000000000..aa2e53295 --- /dev/null +++ b/src/org/apollo/game/model/inter/bank/BankDepositEnterAmountListener.java @@ -0,0 +1,46 @@ +package org.apollo.game.model.inter.bank; + +import org.apollo.game.model.Player; +import org.apollo.game.model.inter.EnterAmountListener; + +/** + * An {@link EnterAmountListener} for depositing items. + * @author Graham + */ +public final class BankDepositEnterAmountListener implements EnterAmountListener { + + /** + * The player. + */ + private final Player player; + + /** + * The item slot. + */ + private final int slot; + + /** + * The item id. + */ + private final int id; + + /** + * Creates the bank deposit amount listener. + * @param player The player. + * @param slot The slot. + * @param id The id. + */ + public BankDepositEnterAmountListener(Player player, int slot, int id) { + this.player = player; + this.slot = slot; + this.id = id; + } + + @Override + public void amountEntered(int amount) { + if (player.getInterfaceSet().contains(BankConstants.BANK_WINDOW_ID)) { + BankUtils.deposit(player, slot, id, amount); + } + } + +} diff --git a/src/org/apollo/game/model/inter/bank/BankInterfaceListener.java b/src/org/apollo/game/model/inter/bank/BankInterfaceListener.java new file mode 100644 index 000000000..926490939 --- /dev/null +++ b/src/org/apollo/game/model/inter/bank/BankInterfaceListener.java @@ -0,0 +1,47 @@ +package org.apollo.game.model.inter.bank; + +import org.apollo.game.model.Player; +import org.apollo.game.model.inter.InterfaceListener; +import org.apollo.game.model.inv.InventoryListener; + +/** + * An {@link InterfaceListener} which removes the {@link InventoryListener}s + * when the bank is closed. + * @author Graham + */ +public final class BankInterfaceListener implements InterfaceListener { + + /** + * The player. + */ + private final Player player; + + /** + * The inventory listener. + */ + private final InventoryListener invListener; + + /** + * The bank listener. + */ + private final InventoryListener bankListener; + + /** + * Creates the bank interface listener. + * @param player The player. + * @param invListener The inventory listener. + * @param bankListener The bank listener. + */ + public BankInterfaceListener(Player player, InventoryListener invListener, InventoryListener bankListener) { + this.player = player; + this.invListener = invListener; + this.bankListener = bankListener; + } + + @Override + public void interfaceClosed() { + player.getInventory().removeListener(invListener); + player.getBank().removeListener(bankListener); + } + +} diff --git a/src/org/apollo/game/model/inter/bank/BankUtils.java b/src/org/apollo/game/model/inter/bank/BankUtils.java new file mode 100644 index 000000000..20cb5a216 --- /dev/null +++ b/src/org/apollo/game/model/inter/bank/BankUtils.java @@ -0,0 +1,144 @@ +package org.apollo.game.model.inter.bank; + +import org.apollo.game.model.Inventory; +import org.apollo.game.model.Item; +import org.apollo.game.model.Player; +import org.apollo.game.model.def.ItemDefinition; +import org.apollo.game.model.inter.InterfaceListener; +import org.apollo.game.model.inv.InventoryListener; +import org.apollo.game.model.inv.SynchronizationInventoryListener; + +/** + * Contains bank-related utility methods. + * @author Graham + */ +public final class BankUtils { + + /** + * Opens a player's bank. + * @param player The player. + */ + public static void openBank(Player player) { + InventoryListener invListener = new SynchronizationInventoryListener(player, BankConstants.SIDEBAR_INVENTORY_ID); + InventoryListener bankListener = new SynchronizationInventoryListener(player, BankConstants.BANK_INVENTORY_ID); + + player.getInventory().addListener(invListener); + player.getBank().addListener(bankListener); + + player.getInventory().forceRefresh(); + player.getBank().forceRefresh(); + + InterfaceListener interListener = new BankInterfaceListener(player, invListener, bankListener); + + player.getInterfaceSet().openWindowWithSidebar(interListener, BankConstants.BANK_WINDOW_ID, BankConstants.SIDEBAR_ID); + } + + /** + * Deposits an item into the player's bank. + * @param player The player. + * @param slot The slot. + * @param id The id. + * @param amount The amount. + * @return {@code false} if the chain should be broken. + */ + public static boolean deposit(Player player, int slot, int id, int amount) { + if (amount == 0) { + return true; + } + + Inventory inventory = player.getInventory(); + Inventory bank = player.getBank(); + + if (slot < 0 || slot >= inventory.capacity()) { + return false; + } + + Item item = inventory.get(slot); + if (item.getId() != id) { + return false; + } + + int newId = ItemDefinition.noteToItem(item.getId()); + + if (bank.freeSlots() == 0 && !bank.contains(item.getId())) { + bank.forceCapacityExceeded(); + return true; + } + + int removed; + if (amount > 1) { + inventory.stopFiringEvents(); + } + try { + removed = inventory.remove(item.getId(), amount); + } finally { + if (amount > 1) { + inventory.startFiringEvents(); + } + } + if (amount > 1) { + inventory.forceRefresh(); + } + bank.add(newId, removed); + + return true; + } + + /** + * Withdraws an item from a player's bank. + * @param player The player. + * @param slot The slot. + * @param id The id. + * @param amount The amount. + * @return {@code false} if the chain should be broken. + */ + public static boolean withdraw(Player player, int slot, int id, int amount) { + if (amount == 0) { + return true; + } + + Inventory inventory = player.getInventory(); + Inventory bank = player.getBank(); + + if (slot < 0 || slot >= bank.capacity()) { + return false; + } + + Item item = bank.get(slot); + if (item == null || item.getId() != id) { + return false; + } + + if (amount >= item.getAmount()) { + amount = item.getAmount(); + } + + int newId = player.isWithdrawingNotes() ? ItemDefinition.itemToNote(item.getId()) : item.getId(); + + if (inventory.freeSlots() == 0 && !(inventory.contains(newId) && ItemDefinition.forId(newId).isStackable())) { + inventory.forceCapacityExceeded(); + return true; + } + + int remaining = inventory.add(newId, amount); + + bank.stopFiringEvents(); + try { + bank.remove(item.getId(), amount - remaining); + bank.shift(); + } finally { + bank.startFiringEvents(); + } + + bank.forceRefresh(); + return true; + } + + /** + * Default private constructor to prevent insantiation. + */ + private BankUtils() { + + } + +} diff --git a/src/org/apollo/game/model/inter/bank/BankWithdrawEnterAmountListener.java b/src/org/apollo/game/model/inter/bank/BankWithdrawEnterAmountListener.java new file mode 100644 index 000000000..c6d2c640d --- /dev/null +++ b/src/org/apollo/game/model/inter/bank/BankWithdrawEnterAmountListener.java @@ -0,0 +1,46 @@ +package org.apollo.game.model.inter.bank; + +import org.apollo.game.model.Player; +import org.apollo.game.model.inter.EnterAmountListener; + +/** + * An {@link EnterAmountListener} for withdrawing items. + * @author Graham + */ +public final class BankWithdrawEnterAmountListener implements EnterAmountListener { + + /** + * The player. + */ + private final Player player; + + /** + * The item slot. + */ + private final int slot; + + /** + * The item id. + */ + private final int id; + + /** + * Creates the bank withdraw amount listener. + * @param player The player. + * @param slot The slot. + * @param id The id. + */ + public BankWithdrawEnterAmountListener(Player player, int slot, int id) { + this.player = player; + this.slot = slot; + this.id = id; + } + + @Override + public void amountEntered(int amount) { + if (player.getInterfaceSet().contains(BankConstants.BANK_WINDOW_ID)) { + BankUtils.withdraw(player, slot, id, amount); + } + } + +} diff --git a/src/org/apollo/game/model/inter/bank/package-info.java b/src/org/apollo/game/model/inter/bank/package-info.java new file mode 100644 index 000000000..c14238459 --- /dev/null +++ b/src/org/apollo/game/model/inter/bank/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains bank-related classes. + */ +package org.apollo.game.model.inter.bank; diff --git a/src/org/apollo/game/model/inter/package-info.java b/src/org/apollo/game/model/inter/package-info.java new file mode 100644 index 000000000..c1bab0948 --- /dev/null +++ b/src/org/apollo/game/model/inter/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains interface listeners. + */ +package org.apollo.game.model.inter; diff --git a/src/org/apollo/game/model/inter/quest/QuestConstants.java b/src/org/apollo/game/model/inter/quest/QuestConstants.java new file mode 100644 index 000000000..6c21d6d83 --- /dev/null +++ b/src/org/apollo/game/model/inter/quest/QuestConstants.java @@ -0,0 +1,39 @@ +package org.apollo.game.model.inter.quest; + +/** + * Contains quest-related constants. + * @author Graham + */ +public final class QuestConstants { + + /** + * The quest interface id. + */ + public static final int QUEST_INTERFACE = 8134; + + /** + * The part of the quest interface that contains the scroll bar. + */ + public static final int QUEST_SCROLL_PANE = 8143; // 8144 = title + + /** + * The array of sub interfaces which display the text. + */ + public static final int[] QUEST_TEXT = { + 8144, 8145, 8147, 8148, 8149, 8150, 8151, 8152, 8153, 8154, 8155, 8156, 8157, 8158, 8159, 8160, 8161, 8162, + 8163, 8164, 8165, 8166, 8167, 8168, 8169, 8170, 8171, 8172, 8173, 8174, 8175, 8176, 8177, 8178, 8179, + 8180, 8181, 8182, 8183, 8184, 8185, 8186, 8187, 8188, 8189, 8190, 8191, 8192, 8193, 8194, 8195, 12174, + 12175, 12176, 12177, 12178, 12179, 12180, 12181, 12182, 12183, 12184, 12185, 12186, 12187, 12188, 12189, + 12190, 12191, 12192, 12193, 12194, 12195, 12196, 12197, 12198, 12199, 12200, 12201, 12202, 12203, 12204, + 12205, 12206, 12207, 12208, 12209, 12210, 12211, 12212, 12213, 12214, 12215, 12216, 12217, 12218, 12219, + 12220, 12221, 12222, 12223 + }; + + /** + * Default private constructor to prevent instantiation. + */ + private QuestConstants() { + + } + +} diff --git a/src/org/apollo/game/model/inter/quest/package-info.java b/src/org/apollo/game/model/inter/quest/package-info.java new file mode 100644 index 000000000..9a27ca695 --- /dev/null +++ b/src/org/apollo/game/model/inter/quest/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains classes related to the quest interfaces. + */ +package org.apollo.game.model.inter.quest; diff --git a/src/org/apollo/game/model/inv/AppearanceInventoryListener.java b/src/org/apollo/game/model/inv/AppearanceInventoryListener.java new file mode 100644 index 000000000..76c14d8e3 --- /dev/null +++ b/src/org/apollo/game/model/inv/AppearanceInventoryListener.java @@ -0,0 +1,45 @@ +package org.apollo.game.model.inv; + +import org.apollo.game.model.Inventory; +import org.apollo.game.model.Item; +import org.apollo.game.model.Player; +import org.apollo.game.sync.block.SynchronizationBlock; + +/** + * An {@link InventoryListener} which updates the player's appearance when + * any items are updated. + * @author Graham + */ +public final class AppearanceInventoryListener extends InventoryAdapter { + + /** + * The player. + */ + private final Player player; + + /** + * Creates the appearance inventory listener. + * @param player The player. + */ + public AppearanceInventoryListener(Player player) { + this.player = player; + } + + /** + * Updates the player's appearance. + */ + private void update() { + player.getBlockSet().add(SynchronizationBlock.createAppearanceBlock(player)); + } + + @Override + public void itemUpdated(Inventory inventory, int slot, Item item) { + update(); + } + + @Override + public void itemsUpdated(Inventory inventory) { + update(); + } + +} diff --git a/src/org/apollo/game/model/inv/FullInventoryListener.java b/src/org/apollo/game/model/inv/FullInventoryListener.java new file mode 100644 index 000000000..a614ca80b --- /dev/null +++ b/src/org/apollo/game/model/inv/FullInventoryListener.java @@ -0,0 +1,55 @@ +package org.apollo.game.model.inv; + +import org.apollo.game.event.Event; +import org.apollo.game.event.impl.ServerMessageEvent; +import org.apollo.game.model.Inventory; +import org.apollo.game.model.Player; + +/** + * An {@link InventoryListener} which sends a message to a player when an + * inventory has run out of space. + * @author Graham + */ +public final class FullInventoryListener extends InventoryAdapter { + + /** + * The inventory full message. + */ + public static final String FULL_INVENTORY_MESSAGE = "Not enough inventory space."; + + /** + * The bank full message. + */ + public static final String FULL_BANK_MESSAGE = "Not enough bank space."; + + /** + * The equipment full message. + */ + public static final String FULL_EQUIPMENT_MESSAGE = "Not enough equipment space."; // TODO confirm if possible + + /** + * The player. + */ + private final Player player; + + /** + * The event to send when the capacity has been exceeded. + */ + private final Event event; + + /** + * Creates the empty inventory listener. + * @param player The player. + * @param message The message to send when the inventory is empty. + */ + public FullInventoryListener(Player player, String message) { + this.player = player; + this.event = new ServerMessageEvent(message); + } + + @Override + public void capacityExceeded(Inventory inventory) { + player.send(event); + } + +} diff --git a/src/org/apollo/game/model/inv/InventoryAdapter.java b/src/org/apollo/game/model/inv/InventoryAdapter.java new file mode 100644 index 000000000..4f959d537 --- /dev/null +++ b/src/org/apollo/game/model/inv/InventoryAdapter.java @@ -0,0 +1,27 @@ +package org.apollo.game.model.inv; + +import org.apollo.game.model.Inventory; +import org.apollo.game.model.Item; + +/** + * An adapter for the {@link InventoryListener}. + * @author Graham + */ +public abstract class InventoryAdapter implements InventoryListener { + + @Override + public void capacityExceeded(Inventory inventory) { + /* empty */ + } + + @Override + public void itemUpdated(Inventory inventory, int slot, Item item) { + /* empty */ + } + + @Override + public void itemsUpdated(Inventory inventory) { + /* empty */ + } + +} diff --git a/src/org/apollo/game/model/inv/InventoryListener.java b/src/org/apollo/game/model/inv/InventoryListener.java new file mode 100644 index 000000000..e0a41ff64 --- /dev/null +++ b/src/org/apollo/game/model/inv/InventoryListener.java @@ -0,0 +1,32 @@ +package org.apollo.game.model.inv; + +import org.apollo.game.model.Inventory; +import org.apollo.game.model.Item; + +/** + * An interface which listens to events from an {@link Inventory}. + * @author Graham + */ +public interface InventoryListener { + + /** + * Called when the capacity of an inventory has been exceeded. + * @param inventory The inventory. + */ + public void capacityExceeded(Inventory inventory); + + /** + * Called when an item has been updated. + * @param inventory The inventory. + * @param slot The slot. + * @param item The new item, or {@code null} if there is no new item. + */ + public void itemUpdated(Inventory inventory, int slot, Item item); + + /** + * Called when items have been updated in bulk. + * @param inventory The inventory. + */ + public void itemsUpdated(Inventory inventory); + +} diff --git a/src/org/apollo/game/model/inv/SynchronizationInventoryListener.java b/src/org/apollo/game/model/inv/SynchronizationInventoryListener.java new file mode 100644 index 000000000..cd00d915c --- /dev/null +++ b/src/org/apollo/game/model/inv/SynchronizationInventoryListener.java @@ -0,0 +1,57 @@ +package org.apollo.game.model.inv; + +import org.apollo.game.event.impl.UpdateItemsEvent; +import org.apollo.game.event.impl.UpdateSlottedItemsEvent; +import org.apollo.game.model.Inventory; +import org.apollo.game.model.Item; +import org.apollo.game.model.Player; +import org.apollo.game.model.SlottedItem; + +/** + * An {@link InventoryListener} which synchronizes the state of the server's + * inventory with the client's. + * @author Graham + */ +public final class SynchronizationInventoryListener extends InventoryAdapter { + + /** + * The inventory interface id. + */ + public static final int INVENTORY_ID = 3214; + + /** + * The equipment interface id. + */ + public static final int EQUIPMENT_ID = 1688; + + /** + * The player. + */ + private final Player player; + + /** + * The interface id. + */ + private final int interfaceId; + + /** + * Creates the syncrhonization inventory listener. + * @param player The player. + * @param interfaceId The interface id. + */ + public SynchronizationInventoryListener(Player player, int interfaceId) { + this.player = player; + this.interfaceId = interfaceId; + } + + @Override + public void itemUpdated(Inventory inventory, int slot, Item item) { + player.send(new UpdateSlottedItemsEvent(interfaceId, new SlottedItem(slot, item))); + } + + @Override + public void itemsUpdated(Inventory inventory) { + player.send(new UpdateItemsEvent(interfaceId, inventory.getItems())); + } + +} diff --git a/src/org/apollo/game/model/inv/package-info.java b/src/org/apollo/game/model/inv/package-info.java new file mode 100644 index 000000000..99da1e1b3 --- /dev/null +++ b/src/org/apollo/game/model/inv/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains inventory listeners. + */ +package org.apollo.game.model.inv; diff --git a/src/org/apollo/game/model/obj/StaticObject.java b/src/org/apollo/game/model/obj/StaticObject.java new file mode 100644 index 000000000..fc4f48c31 --- /dev/null +++ b/src/org/apollo/game/model/obj/StaticObject.java @@ -0,0 +1,24 @@ +package org.apollo.game.model.obj; + +import org.apollo.game.model.def.StaticObjectDefinition; + +/** + * Represents a static object in the game world. + * @author Graham + */ +public final class StaticObject { + + /** + * The object definition. + */ + private final StaticObjectDefinition def; + + /** + * Creates the game object. + * @param def The object's definition. + */ + public StaticObject(StaticObjectDefinition def) { + this.def = def; + } + +} diff --git a/src/org/apollo/game/model/obj/package-info.java b/src/org/apollo/game/model/obj/package-info.java new file mode 100644 index 000000000..da59fb470 --- /dev/null +++ b/src/org/apollo/game/model/obj/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains models related to in-game objects. + */ +package org.apollo.game.model.obj; diff --git a/src/org/apollo/game/model/package-info.java b/src/org/apollo/game/model/package-info.java new file mode 100644 index 000000000..d5a3ac5f5 --- /dev/null +++ b/src/org/apollo/game/model/package-info.java @@ -0,0 +1,5 @@ +/** + * Contains classes which represent things in the in-game world such as items, + * players and NPCs. + */ +package org.apollo.game.model; diff --git a/src/org/apollo/game/model/region/Region.java b/src/org/apollo/game/model/region/Region.java new file mode 100644 index 000000000..3da6fb41d --- /dev/null +++ b/src/org/apollo/game/model/region/Region.java @@ -0,0 +1,14 @@ +package org.apollo.game.model.region; + +/** + * Represents an 8x8 region. + * @author Graham + */ +public final class Region { + + /** + * The width and height of the region in tiles. + */ + public static final int REGION_SIZE = 8; + +} diff --git a/src/org/apollo/game/model/region/RegionCoordinates.java b/src/org/apollo/game/model/region/RegionCoordinates.java new file mode 100644 index 000000000..e8242df46 --- /dev/null +++ b/src/org/apollo/game/model/region/RegionCoordinates.java @@ -0,0 +1,71 @@ +package org.apollo.game.model.region; + +/** + * An immutable class which contains the coordinates of a region. + * @author Graham + */ +public final class RegionCoordinates { + + /** + * The X coordinate. + */ + private final int x; + + /** + * The Y coordinate. + */ + private final int y; + + /** + * Creates the region coordinates. + * @param x The x coordinate. + * @param y The y coordinate. + */ + public RegionCoordinates(int x, int y) { + this.x = x; + this.y = y; + } + + /** + * Gets the X coordinate. + * @return The X coordinate. + */ + public int getX() { + return x; + } + + /** + * Gets the Y coordinate. + * @return The Y coordinate. + */ + public int getY() { + return y; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final RegionCoordinates other = (RegionCoordinates) obj; + if (this.x != other.x) { + return false; + } + if (this.y != other.y) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 61 * hash + this.x; + hash = 61 * hash + this.y; + return hash; + } + +} diff --git a/src/org/apollo/game/model/region/RegionRepository.java b/src/org/apollo/game/model/region/RegionRepository.java new file mode 100644 index 000000000..2d2c19464 --- /dev/null +++ b/src/org/apollo/game/model/region/RegionRepository.java @@ -0,0 +1,9 @@ +package org.apollo.game.model.region; + +/** + * Manages the repository of regions. + * @author Graham + */ +public class RegionRepository { + +} diff --git a/src/org/apollo/game/model/region/package-info.java b/src/org/apollo/game/model/region/package-info.java new file mode 100644 index 000000000..aeef1d23d --- /dev/null +++ b/src/org/apollo/game/model/region/package-info.java @@ -0,0 +1,6 @@ +/** + * Contains classes which represent in-game regions, blocks of 8x8 tiles which + * are grouped, and can be used to store ground items, temporary objects, etc. + * efficiently. + */ +package org.apollo.game.model.region; diff --git a/src/org/apollo/game/model/skill/LevelUpSkillListener.java b/src/org/apollo/game/model/skill/LevelUpSkillListener.java new file mode 100644 index 000000000..35eab5f88 --- /dev/null +++ b/src/org/apollo/game/model/skill/LevelUpSkillListener.java @@ -0,0 +1,36 @@ +package org.apollo.game.model.skill; + +import org.apollo.game.model.Player; +import org.apollo.game.model.Skill; +import org.apollo.game.model.SkillSet; +import org.apollo.util.LanguageUtil; + +/** + * A {@link SkillListener} which notifies the player when they have levelled up + * a skill. + * @author Graham + */ +public final class LevelUpSkillListener extends SkillAdapter { + + /** + * The player. + */ + private final Player player; + + /** + * Creates the level up listener for the specified player. + * @param player The player. + */ + public LevelUpSkillListener(Player player) { + this.player = player; + } + + @Override + public void levelledUp(SkillSet set, int id, Skill skill) { + // TODO show the interface + String name = Skill.getName(id); + String article = LanguageUtil.getIndefiniteArticle(name); + player.sendMessage("You've just advanced " + article + " " + name + " level! You have reached level " + skill.getMaximumLevel() + "."); + } + +} diff --git a/src/org/apollo/game/model/skill/SkillAdapter.java b/src/org/apollo/game/model/skill/SkillAdapter.java new file mode 100644 index 000000000..b348d1c63 --- /dev/null +++ b/src/org/apollo/game/model/skill/SkillAdapter.java @@ -0,0 +1,27 @@ +package org.apollo.game.model.skill; + +import org.apollo.game.model.Skill; +import org.apollo.game.model.SkillSet; + +/** + * An adapter for the {@link SkillListener}. + * @author Graham + */ +public abstract class SkillAdapter implements SkillListener { + + @Override + public void levelledUp(SkillSet set, int id, Skill skill) { + /* empty */ + } + + @Override + public void skillUpdated(SkillSet set, int id, Skill skill) { + /* empty */ + } + + @Override + public void skillsUpdated(SkillSet set) { + /* empty */ + } + +} diff --git a/src/org/apollo/game/model/skill/SkillListener.java b/src/org/apollo/game/model/skill/SkillListener.java new file mode 100644 index 000000000..c6ada64d7 --- /dev/null +++ b/src/org/apollo/game/model/skill/SkillListener.java @@ -0,0 +1,34 @@ +package org.apollo.game.model.skill; + +import org.apollo.game.model.Skill; +import org.apollo.game.model.SkillSet; + +/** + * An interface which listens to events from a {@link SkillSet}. + * @author Graham + */ +public interface SkillListener { + + /** + * Called when a single skill is updated. + * @param set The skill set. + * @param id The skill's id. + * @param skill The skill. + */ + public void skillUpdated(SkillSet set, int id, Skill skill); + + /** + * Called when all the skills are updated. + * @param set The skill set. + */ + public void skillsUpdated(SkillSet set); + + /** + * Called when a skill is levelled up. + * @param set The skill set. + * @param id The skill's id. + * @param skill The skill. + */ + public void levelledUp(SkillSet set, int id, Skill skill); + +} diff --git a/src/org/apollo/game/model/skill/SynchronizationSkillListener.java b/src/org/apollo/game/model/skill/SynchronizationSkillListener.java new file mode 100644 index 000000000..e87176573 --- /dev/null +++ b/src/org/apollo/game/model/skill/SynchronizationSkillListener.java @@ -0,0 +1,46 @@ +package org.apollo.game.model.skill; + +import org.apollo.game.event.impl.UpdateSkillEvent; +import org.apollo.game.model.Player; +import org.apollo.game.model.Skill; +import org.apollo.game.model.SkillSet; +import org.apollo.game.sync.block.SynchronizationBlock; + +/** + * A {@link SkillListener} which synchronizes the state of a {@link SkillSet} + * with a client. + * @author Graham + */ +public final class SynchronizationSkillListener extends SkillAdapter { + + /** + * The player. + */ + private final Player player; + + /** + * Creates the skill synchronization listener. + * @param player The player. + */ + public SynchronizationSkillListener(Player player) { + this.player = player; + } + + @Override + public void levelledUp(SkillSet set, int id, Skill skill) { + player.getBlockSet().add(SynchronizationBlock.createAppearanceBlock(player)); + } + + @Override + public void skillUpdated(SkillSet set, int id, Skill skill) { + player.send(new UpdateSkillEvent(id, skill)); + } + + @Override + public void skillsUpdated(SkillSet set) { + for (int id = 0; id < set.size(); id++) { + player.send(new UpdateSkillEvent(id, set.getSkill(id))); + } + } + +} diff --git a/src/org/apollo/game/model/skill/package-info.java b/src/org/apollo/game/model/skill/package-info.java new file mode 100644 index 000000000..1d4d67947 --- /dev/null +++ b/src/org/apollo/game/model/skill/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains skill listeners. + */ +package org.apollo.game.model.skill; diff --git a/src/org/apollo/game/package-info.java b/src/org/apollo/game/package-info.java new file mode 100644 index 000000000..a63cfcacf --- /dev/null +++ b/src/org/apollo/game/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains classes related to the game server. + */ +package org.apollo.game; diff --git a/src/org/apollo/game/scheduling/ScheduledTask.java b/src/org/apollo/game/scheduling/ScheduledTask.java new file mode 100644 index 000000000..44354a43d --- /dev/null +++ b/src/org/apollo/game/scheduling/ScheduledTask.java @@ -0,0 +1,81 @@ +package org.apollo.game.scheduling; + +/** + * A game-related task that is scheduled to run in the future. + * @author Graham + */ +public abstract class ScheduledTask { + + /** + * A flag indicating if the task is running. + */ + private boolean running = true; + + /** + * The delay between executions of the task, in pulses. + */ + private int delay; + + /** + * The number of pulses remaining until the task is next executed. + */ + private int pulses; + + /** + * Creates a new scheduled task. + * @param delay The delay between executions of the task, in pulses. + * @param immediate A flag indicating if this task should (for the first + * execution) be ran immediately, or after the {@code delay}. + * @throws IllegalArgumentException if the delay is less than or equal to + * zero. + */ + public ScheduledTask(int delay, boolean immediate) { + setDelay(delay); + this.pulses = immediate ? 0 : delay; + } + + /** + * Checks if this task is running. + * @return {@code true} if so, {@code false} if not. + */ + public final boolean isRunning() { + return running; + } + + /** + * Sets the delay. + * @param delay The delay. + * @throws IllegalArgumentException if the delay is less than or equal to + * zero. + */ + public void setDelay(int delay) { + if (delay < 0) { + throw new IllegalArgumentException(); + } + this.delay = delay; + } + + /** + * Stops the task. + */ + public void stop() { + running = false; + } + + /** + * Pulses this task: updates the delay and calls {@link #execute()} if + * necessary. + */ + final void pulse() { + if (running && pulses-- == 0) { + execute(); + pulses = delay; + } + } + + /** + * Executes this task. + */ + public abstract void execute(); + +} diff --git a/src/org/apollo/game/scheduling/Scheduler.java b/src/org/apollo/game/scheduling/Scheduler.java new file mode 100644 index 000000000..698df7195 --- /dev/null +++ b/src/org/apollo/game/scheduling/Scheduler.java @@ -0,0 +1,52 @@ +package org.apollo.game.scheduling; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Queue; + +/** + * A class which manages {@link ScheduledTask}s. + * @author Graham + */ +public final class Scheduler { + + /** + * A queue of new tasks that should be added. + */ + private Queue newTasks = new ArrayDeque(); + + /** + * A list of currently active tasks. + */ + private List tasks = new ArrayList(); + + /** + * Schedules a new task. + * @param task The task to schedule. + */ + public void schedule(ScheduledTask task) { + newTasks.add(task); + } + + /** + * Called every pulse: executes tasks that are still pending, adds new + * tasks and stops old tasks. + */ + public void pulse() { + ScheduledTask task; + while ((task = newTasks.poll()) != null) { + tasks.add(task); + } + + for (Iterator it = tasks.iterator(); it.hasNext(); ) { + task = it.next(); + task.pulse(); + if (!task.isRunning()) { + it.remove(); + } + } + } + +} diff --git a/src/org/apollo/game/scheduling/impl/SkillNormalizationTask.java b/src/org/apollo/game/scheduling/impl/SkillNormalizationTask.java new file mode 100644 index 000000000..3bdd0ccf9 --- /dev/null +++ b/src/org/apollo/game/scheduling/impl/SkillNormalizationTask.java @@ -0,0 +1,36 @@ +package org.apollo.game.scheduling.impl; + +import org.apollo.game.model.Character; +import org.apollo.game.scheduling.ScheduledTask; + +/** + * A {@link ScheduledTask} which normalizes the skills of a player: gradually + * brings them back to their normal value as specified by the experience. + * @author Graham + */ +public final class SkillNormalizationTask extends ScheduledTask { + + /** + * The character. + */ + private final Character character; + + /** + * Creates the skill normalization task. + * @param character The character. + */ + public SkillNormalizationTask(Character character) { + super(100, false); + this.character = character; + } + + @Override + public void execute() { + if (!character.isActive()) { // TODO is this check okay for this? an NPC could be temporarily removed from list + stop(); + } else { + character.getSkillSet().normalize(); + } + } + +} diff --git a/src/org/apollo/game/scheduling/impl/package-info.java b/src/org/apollo/game/scheduling/impl/package-info.java new file mode 100644 index 000000000..1372163d6 --- /dev/null +++ b/src/org/apollo/game/scheduling/impl/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains scheduled task implementations. + */ +package org.apollo.game.scheduling.impl; diff --git a/src/org/apollo/game/scheduling/package-info.java b/src/org/apollo/game/scheduling/package-info.java new file mode 100644 index 000000000..497f26e67 --- /dev/null +++ b/src/org/apollo/game/scheduling/package-info.java @@ -0,0 +1,5 @@ +/** + * Contains classes related to scheduling which allow tasks to be executed in + * future pulses periodically. + */ +package org.apollo.game.scheduling; diff --git a/src/org/apollo/game/sync/ClientSynchronizer.java b/src/org/apollo/game/sync/ClientSynchronizer.java new file mode 100644 index 000000000..756793df5 --- /dev/null +++ b/src/org/apollo/game/sync/ClientSynchronizer.java @@ -0,0 +1,24 @@ +package org.apollo.game.sync; + +/** + * The {@link ClientSynchronizer} manages the update sequence which keeps clients + * synchronized with the in-game world. There are two implementations + * distributed with Apollo: {@link SequentialClientSynchronizer} which is + * optimized for a single-core/single-processor machine and + * {@link ParallelClientSynchronizer} which is optimized for a multi-processor/ + * multi-core machines. + *

+ * To switch between the two synchronizer implementations, edit the + * {@code synchronizers.xml} configuration file. The default implementation is + * currently {@link ParallelClientSynchronizer} as the vast majority of + * machines today have two or more cores. + * @author Graham + */ +public abstract class ClientSynchronizer { + + /** + * Synchronizes the state of the clients with the state of the server. + */ + public abstract void synchronize(); + +} diff --git a/src/org/apollo/game/sync/ParallelClientSynchronizer.java b/src/org/apollo/game/sync/ParallelClientSynchronizer.java new file mode 100644 index 000000000..ff81541e6 --- /dev/null +++ b/src/org/apollo/game/sync/ParallelClientSynchronizer.java @@ -0,0 +1,78 @@ +package org.apollo.game.sync; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Phaser; +import java.util.concurrent.ThreadFactory; + +import org.apollo.game.GameService; +import org.apollo.game.model.Player; +import org.apollo.game.model.World; +import org.apollo.game.sync.task.PhasedSynchronizationTask; +import org.apollo.game.sync.task.PlayerSynchronizationTask; +import org.apollo.game.sync.task.PostPlayerSynchronizationTask; +import org.apollo.game.sync.task.PrePlayerSynchronizationTask; +import org.apollo.game.sync.task.SynchronizationTask; +import org.apollo.util.CharacterRepository; +import org.apollo.util.NamedThreadFactory; + +/** + * An implementation of {@link ClientSynchronizer} which runs in a thread pool. + * A {@link Phaser} is used to ensure that the synchronization is complete, + * allowing control to return to the {@link GameService} that started the + * synchronization. This class will scale well with machines that have multiple + * cores/processors. The {@link SequentialClientSynchronizer} will work better + * on machines with a single core/processor, however, both classes will work. + * @author Graham + */ +public final class ParallelClientSynchronizer extends ClientSynchronizer { + + /** + * The executor service. + */ + private final ExecutorService executor; + + /** + * The phaser. + */ + private final Phaser phaser = new Phaser(1); + + /** + * Creates the parallel client synchronizer backed by a thread pool with a + * number of threads equal to the number of processing cores available + * (this is found by the {@link Runtime#availableProcessors()} method. + */ + public ParallelClientSynchronizer() { + int processors = Runtime.getRuntime().availableProcessors(); + ThreadFactory factory = new NamedThreadFactory("ClientSynchronizer"); + executor = Executors.newFixedThreadPool(processors, factory); + } + + @Override + public void synchronize() { + CharacterRepository players = World.getWorld().getPlayerRepository(); + int playerCount = players.size(); + + phaser.bulkRegister(playerCount); + for (Player player : players) { + SynchronizationTask task = new PrePlayerSynchronizationTask(player); + executor.submit(new PhasedSynchronizationTask(phaser, task)); + } + phaser.arriveAndAwaitAdvance(); + + phaser.bulkRegister(playerCount); + for (Player player : players) { + SynchronizationTask task = new PlayerSynchronizationTask(player); + executor.submit(new PhasedSynchronizationTask(phaser, task)); + } + phaser.arriveAndAwaitAdvance(); + + phaser.bulkRegister(playerCount); + for (Player player : players) { + SynchronizationTask task = new PostPlayerSynchronizationTask(player); + executor.submit(new PhasedSynchronizationTask(phaser, task)); + } + phaser.arriveAndAwaitAdvance(); + } + +} diff --git a/src/org/apollo/game/sync/SequentialClientSynchronizer.java b/src/org/apollo/game/sync/SequentialClientSynchronizer.java new file mode 100644 index 000000000..4508a97db --- /dev/null +++ b/src/org/apollo/game/sync/SequentialClientSynchronizer.java @@ -0,0 +1,43 @@ +package org.apollo.game.sync; + +import org.apollo.game.GameService; +import org.apollo.game.model.Player; +import org.apollo.game.model.World; +import org.apollo.game.sync.task.PlayerSynchronizationTask; +import org.apollo.game.sync.task.PostPlayerSynchronizationTask; +import org.apollo.game.sync.task.PrePlayerSynchronizationTask; +import org.apollo.game.sync.task.SynchronizationTask; +import org.apollo.util.CharacterRepository; + +/** + * An implementation of {@link ClientSynchronizer} which runs in a single + * thread (the {@link GameService} thread from which this is called). Each + * client is processed sequentially. Therefore this class will work well on + * machines with a single core/processor. The {@link ParallelClientSynchronizer} + * will work better on machines with multiple cores/processors, however, both + * classes will work. + * @author Graham + */ +public final class SequentialClientSynchronizer extends ClientSynchronizer { + + @Override + public void synchronize() { + CharacterRepository players = World.getWorld().getPlayerRepository(); + + for (Player player : players) { + SynchronizationTask task = new PrePlayerSynchronizationTask(player); + task.run(); + } + + for (Player player : players) { + SynchronizationTask task = new PlayerSynchronizationTask(player); + task.run(); + } + + for (Player player : players) { + SynchronizationTask task = new PostPlayerSynchronizationTask(player); + task.run(); + } + } + +} diff --git a/src/org/apollo/game/sync/block/AnimationBlock.java b/src/org/apollo/game/sync/block/AnimationBlock.java new file mode 100644 index 000000000..a4a4d06a6 --- /dev/null +++ b/src/org/apollo/game/sync/block/AnimationBlock.java @@ -0,0 +1,32 @@ +package org.apollo.game.sync.block; + +import org.apollo.game.model.Animation; + +/** + * The animation {@link SynchronizationBlock}. + * @author Graham + */ +public final class AnimationBlock extends SynchronizationBlock { + + /** + * The animation. + */ + private final Animation animation; + + /** + * Creates the animation block. + * @param animation The animation. + */ + AnimationBlock(Animation animation) { + this.animation = animation; + } + + /** + * Gets the animation. + * @return The animation. + */ + public Animation getAnimation() { + return animation; + } + +} diff --git a/src/org/apollo/game/sync/block/AppearanceBlock.java b/src/org/apollo/game/sync/block/AppearanceBlock.java new file mode 100644 index 000000000..bfd62dab6 --- /dev/null +++ b/src/org/apollo/game/sync/block/AppearanceBlock.java @@ -0,0 +1,95 @@ +package org.apollo.game.sync.block; + +import org.apollo.game.model.Appearance; +import org.apollo.game.model.Inventory; + +/** + * The appearance {@link SynchronizationBlock}. + * @author Graham + */ +public final class AppearanceBlock extends SynchronizationBlock { + + // TODO head icons support + + /** + * The player's name. + */ + private final long name; + + /** + * The player's appearance. + */ + private final Appearance appearance; + + /** + * The player's combat level. + */ + private final int combat; + + /** + * The player's total skill level (or 0). + */ + private final int skill; + + /** + * The player's equipment. + */ + private final Inventory equipment; + + /** + * Creates the appearance block. + * @param name The player's name. + * @param appearance The appearance. + * @param combat The player's combat. + * @param skill The player's skill, or 0 if showing the combat level. + * @param equipment The player's equipment. + */ + AppearanceBlock(long name, Appearance appearance, int combat, int skill, Inventory equipment) { + this.name = name; + this.appearance = appearance; + this.combat = combat; + this.skill = skill; + this.equipment = equipment.clone(); + } + + /** + * Gets the player's name. + * @return The player's name. + */ + public long getName() { + return name; + } + + /** + * Gets the player's appearance. + * @return The player's appearance. + */ + public Appearance getAppearance() { + return appearance; + } + + /** + * Gets the player's combat level. + * @return The player's combat level. + */ + public int getCombatLevel() { + return combat; + } + + /** + * Gets the player's skill level. + * @return The player's skill level. + */ + public int getSkillLevel() { + return skill; + } + + /** + * Gets the player's equipment. + * @return The player's equipment. + */ + public Inventory getEquipment() { + return equipment; + } + +} diff --git a/src/org/apollo/game/sync/block/ChatBlock.java b/src/org/apollo/game/sync/block/ChatBlock.java new file mode 100644 index 000000000..5087acccc --- /dev/null +++ b/src/org/apollo/game/sync/block/ChatBlock.java @@ -0,0 +1,70 @@ +package org.apollo.game.sync.block; + +import org.apollo.game.event.impl.ChatEvent; +import org.apollo.game.model.Player.PrivilegeLevel; + +/** + * The chat {@link SynchronizationBlock}. + * @author Graham + */ +public final class ChatBlock extends SynchronizationBlock { + + /** + * The privilege level. + */ + private final PrivilegeLevel privilegeLevel; + + /** + * The chat event. + */ + private final ChatEvent chatEvent; + + /** + * Creates the chat block. + */ + ChatBlock(PrivilegeLevel privilegeLevel, ChatEvent chatEvent) { + this.privilegeLevel = privilegeLevel; + this.chatEvent = chatEvent; + } + + /** + * Gets the privilege level of the player who said the message. + * @return The privilege level. + */ + public PrivilegeLevel getPrivilegeLevel() { + return privilegeLevel; + } + + /** + * Gets the message. + * @return The message. + */ + public String getMessage() { + return chatEvent.getMessage(); + } + + /** + * Gets the text color. + * @return The text color. + */ + public int getTextColor() { + return chatEvent.getTextColor(); + } + + /** + * Gets the text effects. + * @return The text effects. + */ + public int getTextEffects() { + return chatEvent.getTextEffects(); + } + + /** + * Gets the compressed message. + * @return The compressed message. + */ + public byte[] getCompressedMessage() { + return chatEvent.getCompressedMessage(); + } + +} diff --git a/src/org/apollo/game/sync/block/GraphicBlock.java b/src/org/apollo/game/sync/block/GraphicBlock.java new file mode 100644 index 000000000..7de0f0d77 --- /dev/null +++ b/src/org/apollo/game/sync/block/GraphicBlock.java @@ -0,0 +1,32 @@ +package org.apollo.game.sync.block; + +import org.apollo.game.model.Graphic; + +/** + * The graphic {@link SynchronizationBlock}. + * @author Graham + */ +public final class GraphicBlock extends SynchronizationBlock { + + /** + * The graphic. + */ + private final Graphic graphic; + + /** + * Creates the graphic block. + * @param graphic The graphic. + */ + GraphicBlock(Graphic graphic) { + this.graphic = graphic; + } + + /** + * Gets the graphic. + * @return The graphic. + */ + public Graphic getGraphic() { + return graphic; + } + +} diff --git a/src/org/apollo/game/sync/block/SynchronizationBlock.java b/src/org/apollo/game/sync/block/SynchronizationBlock.java new file mode 100644 index 000000000..2e5a2be3e --- /dev/null +++ b/src/org/apollo/game/sync/block/SynchronizationBlock.java @@ -0,0 +1,66 @@ +package org.apollo.game.sync.block; + +import org.apollo.game.event.impl.ChatEvent; +import org.apollo.game.model.Animation; +import org.apollo.game.model.Graphic; +import org.apollo.game.model.Player; +import org.apollo.game.model.Position; +import org.apollo.game.sync.seg.SynchronizationSegment; + +/** + * A synchronization block is part of a {@link SynchronizationSegment}. A + * segment can have up to one block of each type. + *

+ * This class also has static factory methods for creating + * {@link SynchronizationBlock}s. + * @author Graham + */ +public abstract class SynchronizationBlock { + + /** + * Creates an appearance block for the specified player. + * @param player The player. + * @return The appearance block. + */ + public static SynchronizationBlock createAppearanceBlock(Player player) { + return new AppearanceBlock(player.getEncodedName(), player.getAppearance(), player.getSkillSet().getCombatLevel(), 0, player.getEquipment()); + } + + /** + * Creates a chat block for the specified player. + * @param player The player. + * @param chatEvent The chat event. + * @return The chat block. + */ + public static SynchronizationBlock createChatBlock(Player player, ChatEvent chatEvent) { + return new ChatBlock(player.getPrivilegeLevel(), chatEvent); + } + + /** + * Creates an animation block with the specified animation. + * @param animation The animation. + * @return The animation block. + */ + public static SynchronizationBlock createAnimationBlock(Animation animation) { + return new AnimationBlock(animation); + } + + /** + * Creates a graphic block with the specified graphic. + * @param graphic The graphic. + * @return The graphic block. + */ + public static SynchronizationBlock createGraphicBlock(Graphic graphic) { + return new GraphicBlock(graphic); + } + + /** + * Creates a turn to position block with the specified position. + * @param position The position. + * @return The turn to position block. + */ + public static SynchronizationBlock createTurnToPositionBlock(Position position) { + return new TurnToPositionBlock(position); + } + +} diff --git a/src/org/apollo/game/sync/block/SynchronizationBlockSet.java b/src/org/apollo/game/sync/block/SynchronizationBlockSet.java new file mode 100644 index 000000000..f8449b8fb --- /dev/null +++ b/src/org/apollo/game/sync/block/SynchronizationBlockSet.java @@ -0,0 +1,76 @@ +package org.apollo.game.sync.block; + +import java.util.HashMap; +import java.util.Map; + +/** + * A specialized collection of {@link SynchronizationBlock}s. + * @author Graham + */ +public final class SynchronizationBlockSet implements Cloneable { + + /** + * The blocks. + */ + private final Map, SynchronizationBlock> blocks = new HashMap, SynchronizationBlock>(); + + /** + * Adds a {@link SynchronizationBlock}. + * @param block The block to add. + */ + public void add(SynchronizationBlock block) { + Class clazz = block.getClass(); + blocks.put(clazz, block); // this will overwrite old updates. best thing to do? + } + + @Override + public SynchronizationBlockSet clone() { + SynchronizationBlockSet copy = new SynchronizationBlockSet(); + copy.blocks.putAll(blocks); + return copy; + } + + /** + * Clears the set. + */ + public void clear() { + blocks.clear(); + } + + /** + * Gets the size of the set. + * @return The size of the set. + */ + public int size() { + return blocks.size(); + } + + /** + * Checks if this set contains the specified block. + * @param clazz The block's class. + * @return {@code true} if so, {@code false} if not. + */ + public boolean contains(Class clazz) { + return blocks.containsKey(clazz); + } + + /** + * Removes a block. + * @param clazz The block's class. + */ + public void remove(Class clazz) { + blocks.remove(clazz); + } + + /** + * Gets a block. + * @param The type of block. + * @param clazz The block's class. + * @return The block. + */ + @SuppressWarnings("unchecked") + public T get(Class clazz) { + return (T) blocks.get(clazz); + } + +} diff --git a/src/org/apollo/game/sync/block/TurnToPositionBlock.java b/src/org/apollo/game/sync/block/TurnToPositionBlock.java new file mode 100644 index 000000000..2888e84d9 --- /dev/null +++ b/src/org/apollo/game/sync/block/TurnToPositionBlock.java @@ -0,0 +1,32 @@ +package org.apollo.game.sync.block; + +import org.apollo.game.model.Position; + +/** + * The turn to position {@link SynchronizationBlock}. + * @author Graham + */ +public final class TurnToPositionBlock extends SynchronizationBlock { + + /** + * The position to turn to. + */ + private final Position position; + + /** + * Creates the turn to position block. + * @param position The position to turn to. + */ + public TurnToPositionBlock(Position position) { + this.position = position; + } + + /** + * Gets the position to turn to. + * @return The position to turn to. + */ + public Position getPosition() { + return position; + } + +} diff --git a/src/org/apollo/game/sync/block/package-info.java b/src/org/apollo/game/sync/block/package-info.java new file mode 100644 index 000000000..6b1923ff0 --- /dev/null +++ b/src/org/apollo/game/sync/block/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains classes related to synchronization 'blocks'. + */ +package org.apollo.game.sync.block; diff --git a/src/org/apollo/game/sync/package-info.java b/src/org/apollo/game/sync/package-info.java new file mode 100644 index 000000000..983126d42 --- /dev/null +++ b/src/org/apollo/game/sync/package-info.java @@ -0,0 +1,5 @@ +/** + * Contains classes related to client synchronization - the process where the + * client's state is updated by the server so it matches the server's state. + */ +package org.apollo.game.sync; diff --git a/src/org/apollo/game/sync/seg/AddCharacterSegment.java b/src/org/apollo/game/sync/seg/AddCharacterSegment.java new file mode 100644 index 000000000..a94ef5a9c --- /dev/null +++ b/src/org/apollo/game/sync/seg/AddCharacterSegment.java @@ -0,0 +1,55 @@ +package org.apollo.game.sync.seg; + +import org.apollo.game.model.Position; +import org.apollo.game.sync.block.SynchronizationBlockSet; + +/** + * A {@link SynchronizationSegment} which adds a character. + * @author Graham + */ +public final class AddCharacterSegment extends SynchronizationSegment { + + /** + * The index. + */ + private final int index; + + /** + * The position. + */ + private final Position position; + + /** + * Creates the add character segment. + * @param blockSet The block set. + * @param index The characters's index. + * @param position The position. + */ + public AddCharacterSegment(SynchronizationBlockSet blockSet, int index, Position position) { + super(blockSet); + this.index = index; + this.position = position; + } + + /** + * Gets the character's index. + * @return The index. + */ + public int getIndex() { + return index; + } + + /** + * Gets the position. + * @return The position. + */ + public Position getPosition() { + return position; + } + + @Override + public SegmentType getType() { + return SegmentType.ADD_CHARACTER; + } + +} diff --git a/src/org/apollo/game/sync/seg/MovementSegment.java b/src/org/apollo/game/sync/seg/MovementSegment.java new file mode 100644 index 000000000..d791e2983 --- /dev/null +++ b/src/org/apollo/game/sync/seg/MovementSegment.java @@ -0,0 +1,53 @@ +package org.apollo.game.sync.seg; + +import org.apollo.game.model.Direction; +import org.apollo.game.sync.block.SynchronizationBlockSet; + +/** + * A {@link SynchronizationSegment} where the character is moved (or doesn't move!). + * @author Graham + */ +public final class MovementSegment extends SynchronizationSegment { + + /** + * The directions. + */ + private final Direction[] directions; + + /** + * Creates the movement segment. + * @param blockSet The block set. + * @param directions The directions array. + * @throws IllegalArgumentException if there are not 0, 1 or 2 directions. + */ + public MovementSegment(SynchronizationBlockSet blockSet, Direction[] directions) { + super(blockSet); + if (directions.length < 0 || directions.length > 2) { + throw new IllegalArgumentException("directions length must be between 0 and 2 inclusive"); + } + this.directions = directions; + } + + /** + * Gets the directions. + * @return The directions. + */ + public Direction[] getDirections() { + return directions; + } + + @Override + public SegmentType getType() { + switch (directions.length) { + case 0: + return SegmentType.NO_MOVEMENT; + case 1: + return SegmentType.WALK; + case 2: + return SegmentType.RUN; + default: + throw new IllegalStateException(); + } + } + +} diff --git a/src/org/apollo/game/sync/seg/RemoveCharacterSegment.java b/src/org/apollo/game/sync/seg/RemoveCharacterSegment.java new file mode 100644 index 000000000..3dbdda4fa --- /dev/null +++ b/src/org/apollo/game/sync/seg/RemoveCharacterSegment.java @@ -0,0 +1,28 @@ +package org.apollo.game.sync.seg; + +import org.apollo.game.sync.block.SynchronizationBlockSet; + +/** + * A {@link SynchronizationSegment} which removes a character. + * @author Graham + */ +public final class RemoveCharacterSegment extends SynchronizationSegment { + + /** + * An empty {@link SynchronizationBlockSet}. + */ + private static final SynchronizationBlockSet EMPTY_BLOCK_SET = new SynchronizationBlockSet(); + + /** + * Creates the remove character segment. + */ + public RemoveCharacterSegment() { + super(EMPTY_BLOCK_SET); + } + + @Override + public SegmentType getType() { + return SegmentType.REMOVE_CHARACTER; + } + +} diff --git a/src/org/apollo/game/sync/seg/SegmentType.java b/src/org/apollo/game/sync/seg/SegmentType.java new file mode 100644 index 000000000..58fed7c95 --- /dev/null +++ b/src/org/apollo/game/sync/seg/SegmentType.java @@ -0,0 +1,39 @@ +package org.apollo.game.sync.seg; + +/** + * An enumeration which contains the types of segments. + * @author Graham + */ +public enum SegmentType { + + /** + * A segment without any movement. + */ + NO_MOVEMENT, + + /** + * A segment with movement in a single direction. + */ + WALK, + + /** + * A segment with movement in two directions. + */ + RUN, + + /** + * A segment where the character is teleported. + */ + TELEPORT, + + /** + * A segment where the character is added. + */ + ADD_CHARACTER, + + /** + * A segment where the character is removed. + */ + REMOVE_CHARACTER; + +} diff --git a/src/org/apollo/game/sync/seg/SynchronizationSegment.java b/src/org/apollo/game/sync/seg/SynchronizationSegment.java new file mode 100644 index 000000000..9b9516a77 --- /dev/null +++ b/src/org/apollo/game/sync/seg/SynchronizationSegment.java @@ -0,0 +1,43 @@ +package org.apollo.game.sync.seg; + +import org.apollo.game.model.Direction; +import org.apollo.game.model.Position; +import org.apollo.game.sync.block.SynchronizationBlock; +import org.apollo.game.sync.block.SynchronizationBlockSet; + +/** + * A segment contains a set of {@link SynchronizationBlock}s, + * {@link Direction}s (or teleport {@link Position}s) and any other things + * required for the update of a single player. + * @author Graham + */ +public abstract class SynchronizationSegment { + + /** + * The {@link SynchronizationBlockSet}. + */ + private final SynchronizationBlockSet blockSet; + + /** + * Creates the segment. + * @param blockSet The block set. + */ + public SynchronizationSegment(SynchronizationBlockSet blockSet) { + this.blockSet = blockSet; + } + + /** + * Gets the block set. + * @return The block set. + */ + public final SynchronizationBlockSet getBlockSet() { + return blockSet; + } + + /** + * Gets the type of segment. + * @return The type of segment. + */ + public abstract SegmentType getType(); + +} diff --git a/src/org/apollo/game/sync/seg/TeleportSegment.java b/src/org/apollo/game/sync/seg/TeleportSegment.java new file mode 100644 index 000000000..2abe8cc8b --- /dev/null +++ b/src/org/apollo/game/sync/seg/TeleportSegment.java @@ -0,0 +1,41 @@ +package org.apollo.game.sync.seg; + +import org.apollo.game.model.Position; +import org.apollo.game.sync.block.SynchronizationBlockSet; + +/** + * A {@link SynchronizationSegment} where the character is teleported to a new + * location. + * @author Graham + */ +public final class TeleportSegment extends SynchronizationSegment { + + /** + * The destination. + */ + private final Position destination; + + /** + * Creates the teleport segment. + * @param blockSet The block set. + * @param destination The destination. + */ + public TeleportSegment(SynchronizationBlockSet blockSet, Position destination) { + super(blockSet); + this.destination = destination; + } + + /** + * Gets the destination. + * @return The destination. + */ + public Position getDestination() { + return destination; + } + + @Override + public SegmentType getType() { + return SegmentType.TELEPORT; + } + +} diff --git a/src/org/apollo/game/sync/seg/package-info.java b/src/org/apollo/game/sync/seg/package-info.java new file mode 100644 index 000000000..e85bdb7bb --- /dev/null +++ b/src/org/apollo/game/sync/seg/package-info.java @@ -0,0 +1,6 @@ +/** + * Contains classes related to synchronization segments. Each segment contains + * multiple blocks and can be used to add, remove, teleport or move a + * character. + */ +package org.apollo.game.sync.seg; diff --git a/src/org/apollo/game/sync/task/PhasedSynchronizationTask.java b/src/org/apollo/game/sync/task/PhasedSynchronizationTask.java new file mode 100644 index 000000000..2dccc29cf --- /dev/null +++ b/src/org/apollo/game/sync/task/PhasedSynchronizationTask.java @@ -0,0 +1,45 @@ +package org.apollo.game.sync.task; + +import java.util.concurrent.Phaser; + +/** + * A {@link SynchronizationTask} which wraps around another + * {@link SynchronizationTask} and notifies the specified {@link Phaser} when + * the task has completed by calling {@link Phaser#arriveAndDeregister()}. + *

+ * The phaser must have already registered this task. This can be done using + * the {@link Phaser#register()} or {@link Phaser#bulkRegister(int)} methods. + * @author Graham + */ +public final class PhasedSynchronizationTask extends SynchronizationTask { + + /** + * The phaser. + */ + private final Phaser phaser; + + /** + * The task. + */ + private final SynchronizationTask task; + + /** + * Creates the phased synchronization task. + * @param phaser The phaser. + * @param task The task. + */ + public PhasedSynchronizationTask(Phaser phaser, SynchronizationTask task) { + this.phaser = phaser; + this.task = task; + } + + @Override + public void run() { + try { + task.run(); + } finally { + phaser.arriveAndDeregister(); + } + } + +} diff --git a/src/org/apollo/game/sync/task/PlayerSynchronizationTask.java b/src/org/apollo/game/sync/task/PlayerSynchronizationTask.java new file mode 100644 index 000000000..770b9dd56 --- /dev/null +++ b/src/org/apollo/game/sync/task/PlayerSynchronizationTask.java @@ -0,0 +1,113 @@ +package org.apollo.game.sync.task; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.apollo.game.event.impl.PlayerSynchronizationEvent; +import org.apollo.game.model.Player; +import org.apollo.game.model.Position; +import org.apollo.game.model.World; +import org.apollo.game.sync.block.AppearanceBlock; +import org.apollo.game.sync.block.ChatBlock; +import org.apollo.game.sync.block.SynchronizationBlock; +import org.apollo.game.sync.block.SynchronizationBlockSet; +import org.apollo.game.sync.seg.AddCharacterSegment; +import org.apollo.game.sync.seg.RemoveCharacterSegment; +import org.apollo.game.sync.seg.MovementSegment; +import org.apollo.game.sync.seg.SynchronizationSegment; +import org.apollo.game.sync.seg.TeleportSegment; +import org.apollo.util.CharacterRepository; + +/** + * A {@link SynchronizationTask} which synchronizes the specified + * {@link Player}. + * @author Graham + */ +public final class PlayerSynchronizationTask extends SynchronizationTask { + + /** + * The maximum number of players to load per cycle. This prevents the + * update packet from becoming too large (the client uses a 5000 byte + * buffer) and also stops old spec PCs from crashing when they login or + * teleport. + */ + private static final int NEW_PLAYERS_PER_CYCLE = 20; + + /** + * The player. + */ + private final Player player; + + /** + * Creates the {@link PlayerSynchronizationTask} for the specified player. + * @param player The player. + */ + public PlayerSynchronizationTask(Player player) { + this.player = player; + } + + @Override + public void run() { + Position lastKnownRegion = player.getLastKnownRegion(); + boolean regionChanged = player.hasRegionChanged(); + + SynchronizationSegment segment; + SynchronizationBlockSet blockSet = player.getBlockSet(); + if (blockSet.contains(ChatBlock.class)) { + blockSet = blockSet.clone(); + blockSet.remove(ChatBlock.class); + } + + if (player.isTeleporting()) { + segment = new TeleportSegment(blockSet, player.getPosition()); + } else { + segment = new MovementSegment(blockSet, player.getDirections()); + } + + List localPlayers = player.getLocalPlayerList(); + int oldLocalPlayers = localPlayers.size(); + 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()) { + it.remove(); + segments.add(new RemoveCharacterSegment()); + } else { + segments.add(new MovementSegment(p.getBlockSet(), p.getDirections())); + } + } + + int added = 0; + + CharacterRepository repository = World.getWorld().getPlayerRepository(); + for (Iterator it = repository.iterator(); it.hasNext(); ) { + Player p = it.next(); + if (localPlayers.size() >= 255) { + player.flagExcessivePlayers(); + break; + } else if (added >= NEW_PLAYERS_PER_CYCLE) { + break; + } + // we do not check p.isActive() here, since if they are active they must be in the repository + if (p != player && p.getPosition().isWithinDistance(player.getPosition(), player.getViewingDistance()) && !localPlayers.contains(p)) { + localPlayers.add(p); + added++; + + blockSet = p.getBlockSet(); + if (!blockSet.contains(AppearanceBlock.class)) { + // TODO check if client has cached appearance + blockSet = blockSet.clone(); + blockSet.add(SynchronizationBlock.createAppearanceBlock(p)); + } + + segments.add(new AddCharacterSegment(blockSet, p.getIndex(), p.getPosition())); + } + } + + PlayerSynchronizationEvent event = new PlayerSynchronizationEvent(lastKnownRegion, player.getPosition(), regionChanged, segment, oldLocalPlayers, segments); + player.send(event); + } + +} diff --git a/src/org/apollo/game/sync/task/PostPlayerSynchronizationTask.java b/src/org/apollo/game/sync/task/PostPlayerSynchronizationTask.java new file mode 100644 index 000000000..d7eaaa57a --- /dev/null +++ b/src/org/apollo/game/sync/task/PostPlayerSynchronizationTask.java @@ -0,0 +1,39 @@ +package org.apollo.game.sync.task; + +import org.apollo.game.model.Player; + +/** + * A {@link SynchronizationTask} which does post-synchronization work for the + * specified {@link Player}. + * @author Graham + */ +public final class PostPlayerSynchronizationTask extends SynchronizationTask { + + /** + * The player. + */ + private final Player player; + + /** + * Creates the {@link PostPlayerSynchronizationTask} for the specified + * player. + * @param player The player. + */ + public PostPlayerSynchronizationTask(Player player) { + this.player = player; + } + + @Override + public void run() { + player.setTeleporting(false); + player.setRegionChanged(false); + player.resetBlockSet(); + if (!player.isExcessivePlayersSet()) { + player.incrementViewingDistance(); + } else { + player.decrementViewingDistance(); + player.resetExcessivePlayers(); + } + } + +} diff --git a/src/org/apollo/game/sync/task/PrePlayerSynchronizationTask.java b/src/org/apollo/game/sync/task/PrePlayerSynchronizationTask.java new file mode 100644 index 000000000..7e1468fd6 --- /dev/null +++ b/src/org/apollo/game/sync/task/PrePlayerSynchronizationTask.java @@ -0,0 +1,65 @@ +package org.apollo.game.sync.task; + +import org.apollo.game.event.impl.RegionChangeEvent; +import org.apollo.game.model.Player; +import org.apollo.game.model.Position; + +/** + * A {@link SynchronizationTask} which does pre-synchronization work for the + * specified {@link Player}. + * @author Graham + */ +public final class PrePlayerSynchronizationTask extends SynchronizationTask { + + /** + * The player. + */ + private final Player player; + + /** + * Creates the {@link PrePlayerSynchronizationTask} for the specified + * player. + * @param player The player. + */ + public PrePlayerSynchronizationTask(Player player) { + this.player = player; + } + + @Override + public void run() { + player.getWalkingQueue().pulse(); + + if (player.isTeleporting()) { + // TODO check if this should be done anywhere else if the conditions should be different + // e.g. if the player teleports one tile away should the viewing distance be reset? + // if this isn't the case, what should the max teleport distance be before it is reset? + // or is this correct anyway?! + player.resetViewingDistance(); + } + + if (!player.hasLastKnownRegion() || isRegionUpdateRequired()) { + player.setTeleporting(true); + player.setRegionChanged(true); + + Position position = player.getPosition(); + player.setLastKnownRegion(position); + + player.send(new RegionChangeEvent(position)); + } + } + + /** + * Checks if a region update is required. + * @return {@code true} if so, {@code false} otherwise. + */ + private boolean isRegionUpdateRequired() { + Position current = player.getPosition(); + Position last = player.getLastKnownRegion(); + + int deltaX = current.getLocalX(last); + int deltaY = current.getLocalY(last); + + return deltaX < 16 || deltaX >= 88 || deltaY < 16 || deltaY >= 88; + } + +} diff --git a/src/org/apollo/game/sync/task/SynchronizationTask.java b/src/org/apollo/game/sync/task/SynchronizationTask.java new file mode 100644 index 000000000..e50c1901c --- /dev/null +++ b/src/org/apollo/game/sync/task/SynchronizationTask.java @@ -0,0 +1,13 @@ +package org.apollo.game.sync.task; + +import org.apollo.game.sync.ClientSynchronizer; + +/** + * Represents some task that must be completed during the client + * synchronization process (see {@link ClientSynchronizer} for more + * information). + * @author Graham + */ +public abstract class SynchronizationTask implements Runnable { + +} diff --git a/src/org/apollo/game/sync/task/package-info.java b/src/org/apollo/game/sync/task/package-info.java new file mode 100644 index 000000000..e8f643183 --- /dev/null +++ b/src/org/apollo/game/sync/task/package-info.java @@ -0,0 +1,6 @@ +/** + * Contains classes related to + * {@link org.apollo.game.sync.task.SynchronizationTask}s, small chunks of work + * executed during the client synchronization process. + */ +package org.apollo.game.sync.task; diff --git a/src/org/apollo/io/EquipmentDefinitionParser.java b/src/org/apollo/io/EquipmentDefinitionParser.java new file mode 100644 index 000000000..a086535a0 --- /dev/null +++ b/src/org/apollo/io/EquipmentDefinitionParser.java @@ -0,0 +1,65 @@ +package org.apollo.io; + +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.apollo.game.model.def.EquipmentDefinition; + +/** + * A class which parses the {@code data/equipment-[release].dat} file to + * create an array of {@link EquipmentDefinition}s. + * @author Graham + */ +public final class EquipmentDefinitionParser { + + /** + * The input stream. + */ + private final InputStream is; + + /** + * Creates the equipment definition parser. + * @param is The input stream. + */ + public EquipmentDefinitionParser(InputStream is) { + this.is = is; + } + + /** + * Parses the input stream. + * @return The equipment definition array. + * @throws IOException if an I/O error occurs. + */ + public EquipmentDefinition[] parse() throws IOException { + DataInputStream dis = new DataInputStream(is); + + int count = dis.readShort() & 0xFFFF; + EquipmentDefinition[] defs = new EquipmentDefinition[count]; + + for (int id = 0; id < count; id++) { + int slot = dis.readByte() & 0xFF; + if (slot != 0xFF) { + boolean twoHanded = dis.readBoolean(); + boolean fullBody = dis.readBoolean(); + boolean fullHat = dis.readBoolean(); + boolean fullMask = dis.readBoolean(); + int attack = dis.readByte() & 0xFF; + int strength = dis.readByte() & 0xFF; + int defence = dis.readByte() & 0xFF; + int ranged = dis.readByte() & 0xFF; + int magic = dis.readByte() & 0xFF; + + EquipmentDefinition def = new EquipmentDefinition(id); + def.setLevels(attack, strength, defence, ranged, magic); + def.setSlot(slot); + def.setFlags(twoHanded, fullBody, fullHat, fullMask); + + defs[id] = def; + } + } + + return defs; + } + +} diff --git a/src/org/apollo/io/EventHandlerChainParser.java b/src/org/apollo/io/EventHandlerChainParser.java new file mode 100644 index 000000000..2fa33bf58 --- /dev/null +++ b/src/org/apollo/io/EventHandlerChainParser.java @@ -0,0 +1,109 @@ +package org.apollo.io; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apollo.game.event.Event; +import org.apollo.game.event.handler.EventHandler; +import org.apollo.game.event.handler.chain.EventHandlerChainGroup; +import org.apollo.game.event.handler.chain.EventHandlerChain; +import org.apollo.util.xml.XmlNode; +import org.apollo.util.xml.XmlParser; +import org.xml.sax.SAXException; + +/** + * A class which parses the {@code events.xml} file to produce + * {@link EventHandlerChainGroup}s. + * @author Graham + */ +public final class EventHandlerChainParser { + + /** + * The {@link XmlParser} instance. + */ + private final XmlParser parser; + + /** + * The source {@link InputStream}. + */ + private final InputStream is; + + /** + * Creates the event chain parser. + * @param is The source {@link InputStream}. + * @throws SAXException if a SAX error occurs. + */ + public EventHandlerChainParser(InputStream is) throws SAXException { + this.parser = new XmlParser(); + this.is = is; + } + + /** + * Parses the XML and produces a group of {@link EventHandlerChain}s. + * @throws IOException if an I/O error occurs. + * @throws SAXException if a SAX error occurs. + * @throws ClassNotFoundException if a class was not found. + * @throws IllegalAccessException if a class was accessed illegally. + * @throws InstantiationException if a class could not be instantiated. + * @return An {@link EventHandlerChainGroup}. + */ + @SuppressWarnings("unchecked") + public EventHandlerChainGroup parse() throws IOException, SAXException, ClassNotFoundException, InstantiationException, IllegalAccessException { + XmlNode rootNode = parser.parse(is); + if (!rootNode.getName().equals("events")) { + throw new IOException("root node name is not 'events'"); + } + + Map, EventHandlerChain> chains = new HashMap, EventHandlerChain>(); + + for (XmlNode eventNode : rootNode) { + if (!eventNode.getName().equals("event")) { + throw new IOException("only expected nodes named 'event' beneath the root node"); + } + + XmlNode typeNode = eventNode.getChild("type"); + if (typeNode == null) { + throw new IOException("no node named 'type' beneath current event node"); + } + XmlNode chainNode = eventNode.getChild("chain"); + if (chainNode == null) { + throw new IOException("no node named 'chain' beneath current event node"); + } + + String eventClassName = typeNode.getValue(); + if (eventClassName == null) { + throw new IOException("type node must have a value"); + } + + Class eventClass = (Class) Class.forName(eventClassName); + List> handlers = new ArrayList>(); + + for (XmlNode handlerNode : chainNode) { + if (!handlerNode.getName().equals("handler")) { + throw new IOException("only expected nodes named 'handler' beneath the root node"); + } + + String handlerClassName = handlerNode.getValue(); + if (handlerClassName == null) { + throw new IOException("handler node must have a value"); + } + + Class> handlerClass = (Class>) Class.forName(handlerClassName); + EventHandler handler = handlerClass.newInstance(); + handlers.add(handler); + } + + EventHandler[] handlersArray = handlers.toArray(new EventHandler[handlers.size()]); + EventHandlerChain chain = new EventHandlerChain(handlersArray); + + chains.put(eventClass, chain); + } + + return new EventHandlerChainGroup(chains); + } + +} diff --git a/src/org/apollo/io/PluginMetaDataParser.java b/src/org/apollo/io/PluginMetaDataParser.java new file mode 100644 index 000000000..740a64c3b --- /dev/null +++ b/src/org/apollo/io/PluginMetaDataParser.java @@ -0,0 +1,119 @@ +package org.apollo.io; + +import java.io.IOException; +import java.io.InputStream; + +import org.apollo.util.plugin.PluginMetaData; +import org.apollo.util.xml.XmlNode; +import org.apollo.util.xml.XmlParser; +import org.xml.sax.SAXException; + +/** + * A class which parses {@code plugin.xml} files into {@link PluginMetaData} + * objects. + * @author Graham + */ +public final class PluginMetaDataParser { + + /** + * An empty xml node array. + */ + private static final XmlNode[] EMPTY_NODE_ARRAY = new XmlNode[0]; + + /** + * The XML parser. + */ + private final XmlParser parser; + + /** + * The input stream. + */ + private final InputStream is; + + /** + * Creates the plugin meta data parser. + * @param is The input stream. + * @throws SAXException if a SAX error occurs. + */ + public PluginMetaDataParser(InputStream is) throws SAXException { + this.parser = new XmlParser(); + this.is = is; + } + + /** + * Parses the XML and creates a meta data object. + * @return The meta data object. + * @throws SAXException if a SAX error occurs. + * @throws IOException if an I/O error occurs. + */ + public PluginMetaData parse() throws IOException, SAXException { + XmlNode rootNode = parser.parse(is); + if (!rootNode.getName().equals("plugin")) { + throw new IOException("root node must be named plugin"); + } + + XmlNode idNode = getElement(rootNode, "id"); + XmlNode nameNode = getElement(rootNode, "name"); + XmlNode descriptionNode = getElement(rootNode, "description"); + XmlNode authorsNode = getElement(rootNode, "authors"); + XmlNode scriptsNode = getElement(rootNode, "scripts"); + XmlNode dependenciesNode = getElement(rootNode, "dependencies"); + XmlNode versionNode = getElement(rootNode, "version"); + + String id = idNode.getValue(); + String name = nameNode.getValue(); + String description = descriptionNode.getValue(); + int version = Integer.parseInt(versionNode.getValue()); + + if (id == null || name == null || description == null) { + throw new IOException("id, name and description must have values"); + } + + XmlNode[] authorNodes = authorsNode.getChildren().toArray(EMPTY_NODE_ARRAY); + XmlNode[] scriptNodes = scriptsNode.getChildren().toArray(EMPTY_NODE_ARRAY); + XmlNode[] dependencyNodes = dependenciesNode.getChildren().toArray(EMPTY_NODE_ARRAY); + + String[] authors = new String[authorNodes.length]; + String[] scripts = new String[scriptNodes.length]; + String[] dependencies = new String[dependencyNodes.length]; + + for (int i = 0; i < authorNodes.length; i++) { + authors[i] = authorNodes[i].getValue(); + if (authors[i] == null) { + throw new IOException("author elements must have values"); + } + } + + for (int i = 0; i < scriptNodes.length; i++) { + scripts[i] = scriptNodes[i].getValue(); + if (scripts[i] == null) { + throw new IOException("script elements must have values"); + } + } + + for (int i = 0; i < dependencyNodes.length; i++) { + dependencies[i] = dependencyNodes[i].getValue(); + if (dependencies[i] == null) { + throw new IOException("dependency elements must have values"); + } + } + + return new PluginMetaData(id, name, description, authors, scripts, dependencies, version); + } + + /** + * Gets the specified child element, if it exists. + * @param node The root node. + * @param name The element name. + * @return The node object. + * @throws IOException if the element does not exist. + */ + private XmlNode getElement(XmlNode node, String name) throws IOException { + XmlNode child = node.getChild(name); + if (child == null) { + throw new IOException("no " + name + " element found"); + } + return child; + } + +} diff --git a/src/org/apollo/io/package-info.java b/src/org/apollo/io/package-info.java new file mode 100644 index 000000000..bffc4b6e7 --- /dev/null +++ b/src/org/apollo/io/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains classes which deal with input/output. + */ +package org.apollo.io; diff --git a/src/org/apollo/io/player/PlayerLoader.java b/src/org/apollo/io/player/PlayerLoader.java new file mode 100644 index 000000000..7e95ff5ce --- /dev/null +++ b/src/org/apollo/io/player/PlayerLoader.java @@ -0,0 +1,21 @@ +package org.apollo.io.player; + +import org.apollo.security.PlayerCredentials; + +/** + * An interface which may be extended by others which are capable of loading + * players. For example, implementations might include text-based, binary and + * SQL loaders. + * @author Graham + */ +public interface PlayerLoader { + + /** + * Loads a player. + * @param credentials The player's credentials. + * @return The {@link PlayerLoaderResponse}. + * @throws Exception if an error occurs. + */ + public PlayerLoaderResponse loadPlayer(PlayerCredentials credentials) throws Exception; + +} diff --git a/src/org/apollo/io/player/PlayerLoaderResponse.java b/src/org/apollo/io/player/PlayerLoaderResponse.java new file mode 100644 index 000000000..0c67bfcae --- /dev/null +++ b/src/org/apollo/io/player/PlayerLoaderResponse.java @@ -0,0 +1,69 @@ +package org.apollo.io.player; + +import org.apollo.game.model.Player; +import org.apollo.net.codec.login.LoginConstants; + +/** + * A response for the + * {@link PlayerLoader#loadPlayer(org.apollo.security.PlayerCredentials)} call. + * @author Graham + */ +public final class PlayerLoaderResponse { + + /** + * The status code. + */ + private final int status; + + /** + * The player. + */ + private final Player player; + + /** + * Creates a {@link PlayerLoaderResponse} with only a status code. + * @param status The status code. + * @throws IllegalArgumentException if the status code needs a + * {@link Player}. + */ + public PlayerLoaderResponse(int status) { + if (status == LoginConstants.STATUS_OK || status == LoginConstants.STATUS_RECONNECTION_OK) { + throw new IllegalArgumentException("player required for this status code"); + } + this.status = status; + this.player = null; + } + + /** + * Creates a {@link PlayerLoaderResponse} with a status code and player. + * @param status The status code. + * @param player The player. + * @throws IllegalArgumentException if the status code does not need + * {@link Player}. + */ + public PlayerLoaderResponse(int status, Player player) { + if (status != LoginConstants.STATUS_OK && status != LoginConstants.STATUS_RECONNECTION_OK) { + throw new IllegalArgumentException("player required for this status code"); + } + this.status = status; + this.player = player; + } + + /** + * Gets the status code. + * @return The status code. + */ + public int getStatus() { + return status; + } + + /** + * Gets the player. + * @return The player, or {@code null} if there is no player in this + * response. + */ + public Player getPlayer() { + return player; + } + +} diff --git a/src/org/apollo/io/player/PlayerSaver.java b/src/org/apollo/io/player/PlayerSaver.java new file mode 100644 index 000000000..5e2533642 --- /dev/null +++ b/src/org/apollo/io/player/PlayerSaver.java @@ -0,0 +1,20 @@ +package org.apollo.io.player; + +import org.apollo.game.model.Player; + +/** + * An interface which may be implemented by others which are capable of saving + * players. For example, implementations might include text-based, binary and + * SQL savers. + * @author Graham + */ +public interface PlayerSaver { + + /** + * Saves a player. + * @param player The player to save. + * @throws Exception if an error occurs. + */ + public void savePlayer(Player player) throws Exception; + +} diff --git a/src/org/apollo/io/player/impl/BinaryPlayerLoader.java b/src/org/apollo/io/player/impl/BinaryPlayerLoader.java new file mode 100644 index 000000000..0a76ea5de --- /dev/null +++ b/src/org/apollo/io/player/impl/BinaryPlayerLoader.java @@ -0,0 +1,130 @@ +package org.apollo.io.player.impl; + +import java.io.DataInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; + +import org.apollo.game.model.Appearance; +import org.apollo.game.model.Gender; +import org.apollo.game.model.Inventory; +import org.apollo.game.model.Item; +import org.apollo.game.model.Player; +import org.apollo.game.model.Position; +import org.apollo.game.model.Skill; +import org.apollo.game.model.SkillSet; +import org.apollo.game.model.Player.PrivilegeLevel; +import org.apollo.io.player.PlayerLoader; +import org.apollo.io.player.PlayerLoaderResponse; +import org.apollo.net.codec.login.LoginConstants; +import org.apollo.security.PlayerCredentials; +import org.apollo.util.StreamUtil; + +/** + * A {@link PlayerLoader} implementation that loads data from a binary file. + * @author Graham + */ +public final class BinaryPlayerLoader implements PlayerLoader { + + /** + * The default spawn position. + */ + private static final Position SPAWN_POSITION = new Position(3222, 3222); + + @Override + public PlayerLoaderResponse loadPlayer(PlayerCredentials credentials) throws Exception { + File f = BinaryPlayerUtil.getFile(credentials.getUsername()); + if (!f.exists()) { + return new PlayerLoaderResponse(LoginConstants.STATUS_OK, new Player(credentials, SPAWN_POSITION)); + } + + DataInputStream in = new DataInputStream(new FileInputStream(f)); + + try { + // read credentials nad privileges + String name = StreamUtil.readString(in); + String pass = StreamUtil.readString(in); + + if (!name.equalsIgnoreCase(credentials.getUsername()) || !pass.equalsIgnoreCase(credentials.getPassword())) { + return new PlayerLoaderResponse(LoginConstants.STATUS_INVALID_CREDENTIALS); + } + + PrivilegeLevel privilegeLevel = PrivilegeLevel.valueOf(in.readByte()); + boolean members = in.readBoolean(); + + // read position + int x = in.readUnsignedShort(); + int y = in.readUnsignedShort(); + int height = in.readUnsignedByte(); + + // read appearance + boolean designedCharacter = in.readBoolean(); + + int genderIntValue = in.readUnsignedByte(); + Gender gender = genderIntValue == Gender.MALE.toInteger() ? Gender.MALE : Gender.FEMALE; + int[] style = new int[7]; + for (int i = 0; i < style.length; i++) { + style[i] = in.readUnsignedByte(); + } + int[] colors = new int[5]; + for (int i = 0; i < colors.length; i++) { + colors[i] = in.readUnsignedByte(); + } + + Player p = new Player(credentials, new Position(x, y, height)); + p.setPrivilegeLevel(privilegeLevel); + p.setMembers(members); + p.setDesignedCharacter(designedCharacter); + p.setAppearance(new Appearance(gender, style, colors)); + + // read inventories + readInventory(in, p.getInventory()); + readInventory(in, p.getEquipment()); + readInventory(in, p.getBank()); + + // read skills + int size = in.readUnsignedByte(); + SkillSet skills = p.getSkillSet(); + skills.stopFiringEvents(); + try { + for (int i = 0; i < size; i++) { + int level = in.readUnsignedByte(); + double experience = in.readDouble(); + skills.setSkill(i, new Skill(experience, level, SkillSet.getLevelForExperience(experience))); + } + } finally { + skills.startFiringEvents(); + } + + return new PlayerLoaderResponse(LoginConstants.STATUS_OK, p); + } finally { + in.close(); + } + } + + /** + * Reads an inventory from the input stream. + * @param in The input stream. + * @param inventory The inventory. + * @throws IOException if an I/O error occurs. + */ + private void readInventory(DataInputStream in, Inventory inventory) throws IOException { + int capacity = in.readUnsignedShort(); + + inventory.stopFiringEvents(); + try { + for (int slot = 0; slot < capacity; slot++) { + int id = in.readUnsignedShort(); + int amount = in.readInt(); + if (id != 0) { + inventory.set(slot, new Item(id - 1, amount)); + } else { + inventory.reset(slot); + } + } + } finally { + inventory.startFiringEvents(); + } + } + +} diff --git a/src/org/apollo/io/player/impl/BinaryPlayerSaver.java b/src/org/apollo/io/player/impl/BinaryPlayerSaver.java new file mode 100644 index 000000000..71f2110a7 --- /dev/null +++ b/src/org/apollo/io/player/impl/BinaryPlayerSaver.java @@ -0,0 +1,96 @@ +package org.apollo.io.player.impl; + +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; + +import org.apollo.game.model.Appearance; +import org.apollo.game.model.Inventory; +import org.apollo.game.model.Item; +import org.apollo.game.model.Player; +import org.apollo.game.model.Position; +import org.apollo.game.model.Skill; +import org.apollo.game.model.SkillSet; +import org.apollo.io.player.PlayerSaver; +import org.apollo.util.StreamUtil; + +/** + * A {@link PlayerSaver} implementation that saves player data to a binary + * file. + * @author Graham + */ +public final class BinaryPlayerSaver implements PlayerSaver { + + @Override + public void savePlayer(Player player) throws Exception { + File f = BinaryPlayerUtil.getFile(player.getName()); + DataOutputStream out = new DataOutputStream(new FileOutputStream(f)); + try { + // write credentials and privileges + StreamUtil.writeString(out, player.getName()); + StreamUtil.writeString(out, player.getCredentials().getPassword()); + out.writeByte(player.getPrivilegeLevel().toInteger()); + out.writeBoolean(player.isMembers()); + + // write position + Position position = player.getPosition(); + out.writeShort(position.getX()); + out.writeShort(position.getY()); + out.writeByte(position.getHeight()); + + // write appearance + out.writeBoolean(player.hasDesignedCharacter()); + Appearance appearance = player.getAppearance(); + out.writeByte(appearance.getGender().toInteger()); + int[] style = appearance.getStyle(); + for (int i = 0; i < style.length; i++) { + out.writeByte(style[i]); + } + int[] colors = appearance.getColors(); + for (int i = 0; i < colors.length; i++) { + out.writeByte(colors[i]); + } + out.flush(); + + // write inventories + writeInventory(out, player.getInventory()); + writeInventory(out, player.getEquipment()); + writeInventory(out, player.getBank()); + + // write skills + SkillSet skills = player.getSkillSet(); + out.writeByte(skills.size()); + for (int i = 0; i < skills.size(); i++) { + Skill skill = skills.getSkill(i); + out.writeByte(skill.getCurrentLevel()); + out.writeDouble(skill.getExperience()); + } + } finally { + out.close(); + } + } + + /** + * Writes an inventory to the specified output stream. + * @param out The output stream. + * @param inventory The inventory. + * @throws IOException if an I/O error occurs. + */ + private void writeInventory(DataOutputStream out, Inventory inventory) throws IOException { + int capacity = inventory.capacity(); + out.writeShort(capacity); + + for (int slot = 0; slot < capacity; slot++) { + Item item = inventory.get(slot); + if (item != null) { + out.writeShort(item.getId() + 1); + out.writeInt(item.getAmount()); + } else { + out.writeShort(0); + out.writeInt(0); + } + } + } + +} diff --git a/src/org/apollo/io/player/impl/BinaryPlayerUtil.java b/src/org/apollo/io/player/impl/BinaryPlayerUtil.java new file mode 100644 index 000000000..215815bf7 --- /dev/null +++ b/src/org/apollo/io/player/impl/BinaryPlayerUtil.java @@ -0,0 +1,45 @@ +package org.apollo.io.player.impl; + +import java.io.File; + +import org.apollo.util.NameUtil; + +/** + * A utility class with common functionality used by the binary player loader/ + * savers. + * @author Graham + */ +public final class BinaryPlayerUtil { + + /** + * The saved games directory. + */ + private static final File SAVED_GAMES_DIRECTORY = new File("data/savedGames"); + + /** + * Creates the saved games directory if it does not exist. + */ + static { + if (!SAVED_GAMES_DIRECTORY.exists()) { + SAVED_GAMES_DIRECTORY.mkdir(); + } + } + + /** + * Gets the file for the specified player. + * @param name The name of the player. + * @return The file. + */ + public static File getFile(String name) { + name = NameUtil.decodeBase37(NameUtil.encodeBase37(name)); + return new File(SAVED_GAMES_DIRECTORY, name + ".dat"); + } + + /** + * Default private constructor to prevent instantiation. + */ + private BinaryPlayerUtil() { + + } + +} diff --git a/src/org/apollo/io/player/impl/DiscardPlayerSaver.java b/src/org/apollo/io/player/impl/DiscardPlayerSaver.java new file mode 100644 index 000000000..bd0a52025 --- /dev/null +++ b/src/org/apollo/io/player/impl/DiscardPlayerSaver.java @@ -0,0 +1,17 @@ +package org.apollo.io.player.impl; + +import org.apollo.game.model.Player; +import org.apollo.io.player.PlayerSaver; + +/** + * A {@link PlayerSaver} implementation that discards player data. + * @author Graham + */ +public final class DiscardPlayerSaver implements PlayerSaver { + + @Override + public void savePlayer(Player player) throws Exception { + /* discard player */ + } + +} diff --git a/src/org/apollo/io/player/impl/DummyPlayerLoader.java b/src/org/apollo/io/player/impl/DummyPlayerLoader.java new file mode 100644 index 000000000..ab4781fc4 --- /dev/null +++ b/src/org/apollo/io/player/impl/DummyPlayerLoader.java @@ -0,0 +1,33 @@ +package org.apollo.io.player.impl; + +import org.apollo.game.model.Player; +import org.apollo.game.model.Position; +import org.apollo.game.model.Player.PrivilegeLevel; +import org.apollo.io.player.PlayerLoader; +import org.apollo.io.player.PlayerLoaderResponse; +import org.apollo.net.codec.login.LoginConstants; +import org.apollo.security.PlayerCredentials; + +/** + * A dummy {@link PlayerLoader} implementation used for testing purposes. + * @author Graham + */ +public final class DummyPlayerLoader implements PlayerLoader { + + /** + * The default spawn position for players loaded by this loader. + */ + private static final Position DEFAULT_POSITION = new Position(3222, 3222); + + @Override + public PlayerLoaderResponse loadPlayer(PlayerCredentials credentials) throws Exception { + int status = LoginConstants.STATUS_OK; + + Player player = new Player(credentials, DEFAULT_POSITION); + player.setPrivilegeLevel(PrivilegeLevel.ADMINISTRATOR); + player.setMembers(true); + + return new PlayerLoaderResponse(status, player); + } + +} diff --git a/src/org/apollo/io/player/impl/JdbcPlayerLoader.java b/src/org/apollo/io/player/impl/JdbcPlayerLoader.java new file mode 100644 index 000000000..32b8d6d6a --- /dev/null +++ b/src/org/apollo/io/player/impl/JdbcPlayerLoader.java @@ -0,0 +1,15 @@ +package org.apollo.io.player.impl; + +import org.apollo.io.player.PlayerLoader; +import org.apollo.io.player.PlayerLoaderResponse; +import org.apollo.security.PlayerCredentials; + +public final class JdbcPlayerLoader implements PlayerLoader { + + @Override + public PlayerLoaderResponse loadPlayer(PlayerCredentials credentials) throws Exception { + + return null; + } + +} diff --git a/src/org/apollo/io/player/impl/JdbcPlayerSaver.java b/src/org/apollo/io/player/impl/JdbcPlayerSaver.java new file mode 100644 index 000000000..e4382f255 --- /dev/null +++ b/src/org/apollo/io/player/impl/JdbcPlayerSaver.java @@ -0,0 +1,13 @@ +package org.apollo.io.player.impl; + +import org.apollo.game.model.Player; +import org.apollo.io.player.PlayerSaver; + +public final class JdbcPlayerSaver implements PlayerSaver { + + @Override + public void savePlayer(Player player) throws Exception { + + } + +} diff --git a/src/org/apollo/io/player/impl/package-info.java b/src/org/apollo/io/player/impl/package-info.java new file mode 100644 index 000000000..4557451d4 --- /dev/null +++ b/src/org/apollo/io/player/impl/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains various player loader/saver implementations. + */ +package org.apollo.io.player.impl; diff --git a/src/org/apollo/io/player/package-info.java b/src/org/apollo/io/player/package-info.java new file mode 100644 index 000000000..c54e147da --- /dev/null +++ b/src/org/apollo/io/player/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains classes which deal with loading and saving player files. + */ +package org.apollo.io.player; diff --git a/src/org/apollo/login/LoginService.java b/src/org/apollo/login/LoginService.java new file mode 100644 index 000000000..80a6c4862 --- /dev/null +++ b/src/org/apollo/login/LoginService.java @@ -0,0 +1,119 @@ +package org.apollo.login; + +import java.io.FileInputStream; +import java.io.InputStream; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import org.apollo.Service; +import org.apollo.game.model.Player; +import org.apollo.io.player.PlayerLoader; +import org.apollo.io.player.PlayerLoaderResponse; +import org.apollo.io.player.PlayerSaver; +import org.apollo.net.codec.login.LoginConstants; +import org.apollo.net.codec.login.LoginRequest; +import org.apollo.net.release.Release; +import org.apollo.net.session.GameSession; +import org.apollo.net.session.LoginSession; +import org.apollo.util.NamedThreadFactory; +import org.apollo.util.xml.XmlNode; +import org.apollo.util.xml.XmlParser; + +/** + * The {@link LoginService} manages {@link LoginRequest}s. + * @author Graham + */ +public final class LoginService extends Service { + + /** + * The {@link ExecutorService} to which workers are submitted. + */ + private final ExecutorService executor = Executors.newCachedThreadPool(new NamedThreadFactory("LoginService")); + + /** + * The current {@link PlayerLoader}. + */ + private PlayerLoader loader; + + /** + * The current {@link PlayerSaver}. + */ + private PlayerSaver saver; + + /** + * Creates the login service. + * @throws Exception if an error occurs. + */ + public LoginService() throws Exception { + init(); + } + + /** + * Initialises the login service. + * @throws Exception if an error occurs. + */ + private void init() throws Exception { + XmlParser parser = new XmlParser(); + XmlNode rootNode; + + InputStream is = new FileInputStream("data/login.xml"); + try { + rootNode = parser.parse(is); + } finally { + is.close(); + } + + if (!rootNode.getName().equals("login")) { + throw new Exception("unexpected root node name"); + } + + XmlNode loaderNode = rootNode.getChild("loader"); + if (loaderNode == null || !loaderNode.hasValue()) { + throw new Exception("no loader child node or value"); + } + + XmlNode saverNode = rootNode.getChild("saver"); + if (saverNode == null || !saverNode.hasValue()) { + throw new Exception("no saver child node or value"); + } + + Class loaderClazz = Class.forName(loaderNode.getValue()); + Class saverClazz = Class.forName(saverNode.getValue()); + + loader = (PlayerLoader) loaderClazz.newInstance(); + saver = (PlayerSaver) saverClazz.newInstance(); + } + + /** + * Submits a login request. + * @param session The session submitting this request. + * @param request The login request. + */ + public void submitLoadRequest(LoginSession session, LoginRequest request) { + Release release = session.getRelease(); + if (release.getReleaseNumber() != request.getReleaseNumber()) { + // TODO check archive 0 CRCs + session.handlePlayerLoaderResponse(request, new PlayerLoaderResponse(LoginConstants.STATUS_GAME_UPDATED)); + } else { + executor.submit(new PlayerLoaderWorker(loader, session, request)); + } + } + + /** + * Submits a save request. + * @param session The session submitting this request. + * @param player The player to save. + */ + public void submitSaveRequest(GameSession session, Player player) { + executor.submit(new PlayerSaverWorker(saver, session, player)); + } + + /** + * Starts the login service. + */ + @Override + public void start() { + /* empty - here for consistency with other services */ + } + +} diff --git a/src/org/apollo/login/PlayerLoaderWorker.java b/src/org/apollo/login/PlayerLoaderWorker.java new file mode 100644 index 000000000..0412af7e7 --- /dev/null +++ b/src/org/apollo/login/PlayerLoaderWorker.java @@ -0,0 +1,62 @@ +package org.apollo.login; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.apollo.io.player.PlayerLoader; +import org.apollo.io.player.PlayerLoaderResponse; +import org.apollo.net.codec.login.LoginConstants; +import org.apollo.net.codec.login.LoginRequest; +import org.apollo.net.session.LoginSession; + +/** + * A class which processes a single login request. + * @author Graham + */ +public final class PlayerLoaderWorker implements Runnable { + + /** + * The logger for this class. + */ + private static final Logger logger = Logger.getLogger(PlayerLoaderWorker.class.getName()); + + /** + * The player loader. + */ + private final PlayerLoader loader; + + /** + * The session that submitted the request. + */ + private final LoginSession session; + + /** + * The request. + */ + private final LoginRequest request; + + /** + * Creates a {@link PlayerLoaderWorker} which will do the work for a single + * player load request. + * @param loader The current player loader. + * @param session The {@link LoginSession} which initiated the request. + * @param request The {@link LoginRequest} object. + */ + public PlayerLoaderWorker(PlayerLoader loader, LoginSession session, LoginRequest request) { + this.loader = loader; + this.session = session; + this.request = request; + } + + @Override + public void run() { + try { + PlayerLoaderResponse response = loader.loadPlayer(request.getCredentials()); + session.handlePlayerLoaderResponse(request, response); + } catch (Exception e) { + logger.log(Level.SEVERE, "Unable to load player's game.", e); + session.handlePlayerLoaderResponse(request, new PlayerLoaderResponse(LoginConstants.STATUS_COULD_NOT_COMPLETE)); + } + } + +} diff --git a/src/org/apollo/login/PlayerSaverWorker.java b/src/org/apollo/login/PlayerSaverWorker.java new file mode 100644 index 000000000..20d2ce069 --- /dev/null +++ b/src/org/apollo/login/PlayerSaverWorker.java @@ -0,0 +1,59 @@ +package org.apollo.login; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.apollo.game.model.Player; +import org.apollo.io.player.PlayerSaver; +import org.apollo.net.session.GameSession; + +/** + * A class which processes a single save request. + * @author Graham + */ +public final class PlayerSaverWorker implements Runnable { + + /** + * The logger for this class. + */ + private static final Logger logger = Logger.getLogger(PlayerSaverWorker.class.getName()); + + /** + * The player saver. + */ + private final PlayerSaver saver; + + /** + * The game session. + */ + private final GameSession session; + + /** + * The player to save. + */ + private final Player player; + + /** + * Creates the player saver worker. + * @param saver The player saver. + * @param session The game session. + * @param player The player to save. + */ + public PlayerSaverWorker(PlayerSaver saver, GameSession session, Player player) { + this.saver = saver; + this.session = session; + this.player = player; + } + + @Override + public void run() { + try { + saver.savePlayer(player); + session.handlePlayerSaverResponse(true); + } catch (Exception e) { + logger.log(Level.SEVERE, "Unable to save player's game.", e); + session.handlePlayerSaverResponse(false); + } + } + +} diff --git a/src/org/apollo/login/package-info.java b/src/org/apollo/login/package-info.java new file mode 100644 index 000000000..1632fa8f5 --- /dev/null +++ b/src/org/apollo/login/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains classes related to the login service. + */ +package org.apollo.login; diff --git a/src/org/apollo/net/ApolloHandler.java b/src/org/apollo/net/ApolloHandler.java new file mode 100644 index 000000000..373da45f9 --- /dev/null +++ b/src/org/apollo/net/ApolloHandler.java @@ -0,0 +1,104 @@ +package org.apollo.net; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.apollo.ServerContext; +import org.apollo.net.codec.handshake.HandshakeConstants; +import org.apollo.net.codec.handshake.HandshakeMessage; +import org.apollo.net.codec.jaggrab.JagGrabRequest; +import org.apollo.net.session.LoginSession; +import org.apollo.net.session.Session; +import org.apollo.net.session.UpdateSession; +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.channel.ChannelStateEvent; +import org.jboss.netty.channel.ExceptionEvent; +import org.jboss.netty.channel.MessageEvent; +import org.jboss.netty.channel.SimpleChannelUpstreamHandler; +import org.jboss.netty.handler.codec.http.HttpRequest; +import org.jboss.netty.handler.timeout.IdleStateAwareChannelUpstreamHandler; +import org.jboss.netty.handler.timeout.IdleStateEvent; + +/** + * An implementation of {@link SimpleChannelUpstreamHandler} which handles + * incoming upstream events from Netty. + * @author Graham + */ +public final class ApolloHandler extends IdleStateAwareChannelUpstreamHandler { + + /** + * The logger for this class. + */ + private static final Logger logger = Logger.getLogger(ApolloHandler.class.getName()); + + /** + * The server context. + */ + private final ServerContext serverContext; + + /** + * Creates the Apollo event handler. + * @param context The server context. + */ + public ApolloHandler(ServerContext context) { + this.serverContext = context; + } + + @Override + public void channelIdle(ChannelHandlerContext ctx, IdleStateEvent e) throws Exception { + e.getChannel().close(); + } + + @Override + public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { + Channel channel = ctx.getChannel(); + logger.info("Channel connected: " + channel); + serverContext.getChannelGroup().add(channel); + } + + @Override + public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { + Channel channel = ctx.getChannel(); + logger.info("Channel disconnected: " + channel); + serverContext.getChannelGroup().remove(channel); + Object attachment = ctx.getAttachment(); + if (attachment != null) { + ((Session) attachment).destroy(); + } + } + + @Override + public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { + if (ctx.getAttachment() == null) { + Object msg = e.getMessage(); + if (msg instanceof HttpRequest || msg instanceof JagGrabRequest) { + Session s = new UpdateSession(ctx.getChannel(), serverContext); + s.messageReceived(msg); + // we don't bother to set it as an attachment, as the connection + // will be closed once the request is completed anyway + } else { + HandshakeMessage handshakeMessage = (HandshakeMessage) msg; + switch (handshakeMessage.getServiceId()) { + case HandshakeConstants.SERVICE_GAME: + ctx.setAttachment(new LoginSession(ctx.getChannel(), ctx, serverContext)); + break; + case HandshakeConstants.SERVICE_UPDATE: + ctx.setAttachment(new UpdateSession(ctx.getChannel(), serverContext)); + break; + default: + throw new Exception("Invalid service id"); + } + } + } else { + ((Session) ctx.getAttachment()).messageReceived(e.getMessage()); + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception { + logger.log(Level.WARNING, "Exception occured for channel: " + e.getChannel() + ", closing...", e.getCause()); + ctx.getChannel().close(); + } + +} diff --git a/src/org/apollo/net/HttpPipelineFactory.java b/src/org/apollo/net/HttpPipelineFactory.java new file mode 100644 index 000000000..243453339 --- /dev/null +++ b/src/org/apollo/net/HttpPipelineFactory.java @@ -0,0 +1,61 @@ +package org.apollo.net; + +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.channel.ChannelPipelineFactory; +import org.jboss.netty.channel.Channels; +import org.jboss.netty.handler.codec.http.HttpChunkAggregator; +import org.jboss.netty.handler.codec.http.HttpRequestDecoder; +import org.jboss.netty.handler.codec.http.HttpResponseEncoder; +import org.jboss.netty.handler.timeout.IdleStateHandler; +import org.jboss.netty.util.Timer; + +/** + * A {@link ChannelPipelineFactory} for the HTTP protocol. + * @author Graham + */ +public final class HttpPipelineFactory implements ChannelPipelineFactory { + + /** + * The maximum length of a request, in bytes. + */ + private static final int MAX_REQUEST_LENGTH = 8192; + + /** + * The server event handler. + */ + private final ApolloHandler handler; + + /** + * The timer used for idle checking. + */ + private final Timer timer; + + /** + * Creates the HTTP pipeline factory. + * @param handler The file server event handler. + * @param timer The timer used for idle checking. + */ + public HttpPipelineFactory(ApolloHandler handler, Timer timer) { + this.handler = handler; + this.timer = timer; + } + + @Override + public ChannelPipeline getPipeline() throws Exception { + ChannelPipeline pipeline = Channels.pipeline(); + + // decoders + pipeline.addLast("decoder", new HttpRequestDecoder()); + pipeline.addLast("chunker", new HttpChunkAggregator(MAX_REQUEST_LENGTH)); + + // encoders + pipeline.addLast("encoder", new HttpResponseEncoder()); + + // handler + pipeline.addLast("timeout", new IdleStateHandler(timer, NetworkConstants.IDLE_TIME, 0, 0)); + pipeline.addLast("handler", handler); + + return pipeline; + } + +} diff --git a/src/org/apollo/net/JagGrabPipelineFactory.java b/src/org/apollo/net/JagGrabPipelineFactory.java new file mode 100644 index 000000000..e5ab62874 --- /dev/null +++ b/src/org/apollo/net/JagGrabPipelineFactory.java @@ -0,0 +1,85 @@ +package org.apollo.net; + +import java.nio.charset.Charset; + +import org.apollo.net.codec.jaggrab.JagGrabRequestDecoder; +import org.apollo.net.codec.jaggrab.JagGrabResponseEncoder; +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.buffer.ChannelBuffers; +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.channel.ChannelPipelineFactory; +import org.jboss.netty.channel.Channels; +import org.jboss.netty.handler.codec.frame.DelimiterBasedFrameDecoder; +import org.jboss.netty.handler.codec.string.StringDecoder; +import org.jboss.netty.handler.timeout.IdleStateHandler; +import org.jboss.netty.util.Timer; + +/** + * A {@link ChannelPipelineFactory} for the JAGGRAB protocol. + * @author Graham + */ +public final class JagGrabPipelineFactory implements ChannelPipelineFactory { + + /** + * The maximum length of a request, in bytes. + */ + private static final int MAX_REQUEST_LENGTH = 8192; + + /** + * The character set used in the request. + */ + private static final Charset JAGGRAB_CHARSET = Charset.forName("US-ASCII"); + + /** + * A buffer with two line feed (LF) characters in it. + */ + private static final ChannelBuffer DOUBLE_LINE_FEED_DELIMITER = ChannelBuffers.buffer(2); + + /** + * Populates the double line feed buffer. + */ + static { + DOUBLE_LINE_FEED_DELIMITER.writeByte(10); + DOUBLE_LINE_FEED_DELIMITER.writeByte(10); + } + + /** + * The file server event handler. + */ + private final ApolloHandler handler; + + /** + * The timer used for idle checking. + */ + private final Timer timer; + + /** + * Creates a {@code JAGGRAB} pipeline factory. + * @param handler The file server event handler. + * @param timer The timer used for idle checking. + */ + public JagGrabPipelineFactory(ApolloHandler handler, Timer timer) { + this.handler = handler; + this.timer = timer; + } + + @Override + public ChannelPipeline getPipeline() throws Exception { + ChannelPipeline pipeline = Channels.pipeline(); + + // decoders + pipeline.addLast("framer", new DelimiterBasedFrameDecoder(MAX_REQUEST_LENGTH, DOUBLE_LINE_FEED_DELIMITER)); + pipeline.addLast("string-decoder", new StringDecoder(JAGGRAB_CHARSET)); + pipeline.addLast("jaggrab-decoder", new JagGrabRequestDecoder()); + + // encoders + pipeline.addLast("jaggrab-encoder", new JagGrabResponseEncoder()); + + // handler + pipeline.addLast("timeout", new IdleStateHandler(timer, NetworkConstants.IDLE_TIME, 0, 0)); + pipeline.addLast("handler", handler); + + return pipeline; + } + +} diff --git a/src/org/apollo/net/NetworkConstants.java b/src/org/apollo/net/NetworkConstants.java new file mode 100644 index 000000000..6e018ad02 --- /dev/null +++ b/src/org/apollo/net/NetworkConstants.java @@ -0,0 +1,41 @@ +package org.apollo.net; + +/** + * Holds various network-related constants such as port numbers. + * @author Graham + */ +public final class NetworkConstants { + + /** + * The service port. + */ + public static final int SERVICE_PORT = 43594; + + /** + * The JAGGRAB port. + */ + public static final int JAGGRAB_PORT = 43595; + + /** + * The HTTP port. + */ + public static final int HTTP_PORT = 80; + + /** + * The terminator of a string. + */ + public static final int STRING_TERMINATOR = 10; + + /** + * The number of seconds before a connection becomes idle. + */ + public static final int IDLE_TIME = 15; + + /** + * Default private constructor to prevent instantiation by other classes. + */ + private NetworkConstants() { + + } + +} diff --git a/src/org/apollo/net/ServicePipelineFactory.java b/src/org/apollo/net/ServicePipelineFactory.java new file mode 100644 index 000000000..12deb7646 --- /dev/null +++ b/src/org/apollo/net/ServicePipelineFactory.java @@ -0,0 +1,46 @@ +package org.apollo.net; + +import org.apollo.net.codec.handshake.HandshakeDecoder; +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.channel.ChannelPipelineFactory; +import org.jboss.netty.channel.Channels; +import org.jboss.netty.handler.timeout.IdleStateHandler; +import org.jboss.netty.util.Timer; + +/** + * A {@link ChannelPipelineFactory} which creates {@link ChannelPipeline}s for + * the service pipeline. + * @author Graham + */ +public final class ServicePipelineFactory implements ChannelPipelineFactory { + + /** + * The network event handler. + */ + private final ApolloHandler handler; + + /** + * The timer used for idle checking. + */ + private final Timer timer; + + /** + * Creates the service pipeline factory. + * @param handler The networking event handler. + * @param timer The timer used for idle checking. + */ + public ServicePipelineFactory(ApolloHandler handler, Timer timer) { + this.handler = handler; + this.timer = timer; + } + + @Override + public ChannelPipeline getPipeline() throws Exception { + ChannelPipeline pipeline = Channels.pipeline(); + pipeline.addLast("handshakeDecoder", new HandshakeDecoder()); + pipeline.addLast("timeout", new IdleStateHandler(timer, NetworkConstants.IDLE_TIME, 0, 0)); + pipeline.addLast("handler", handler); + return pipeline; + } + +} diff --git a/src/org/apollo/net/codec/game/AccessMode.java b/src/org/apollo/net/codec/game/AccessMode.java new file mode 100644 index 000000000..5277c05b6 --- /dev/null +++ b/src/org/apollo/net/codec/game/AccessMode.java @@ -0,0 +1,20 @@ +package org.apollo.net.codec.game; + +/** + * An enumeration which holds the mode a {@link GamePacketBuilder} or + * {@link GamePacketReader} can be in. + * @author Graham + */ +public enum AccessMode { + + /** + * When in byte access modes, bytes are written directly to the buffer. + */ + BYTE_ACCESS, + + /** + * When in bit access mode, bits can be written and packed into bytes. + */ + BIT_ACCESS; + +} diff --git a/src/org/apollo/net/codec/game/DataConstants.java b/src/org/apollo/net/codec/game/DataConstants.java new file mode 100644 index 000000000..11c74cccb --- /dev/null +++ b/src/org/apollo/net/codec/game/DataConstants.java @@ -0,0 +1,31 @@ +package org.apollo.net.codec.game; + +/** + * A class holding data-related constants. + * @author Graham + */ +public final class DataConstants { + + /** + * An array of bit masks. The element {@code n} is equal to + * {@code 2n - 1}. + */ + public static final int[] BIT_MASK = new int[32]; + + /** + * Initializes the {@link #BIT_MASK} array. + */ + static { + for (int i = 0; i < BIT_MASK.length; i++) { + BIT_MASK[i] = ((1 << i) - 1); + } + } + + /** + * Default private constructor to prevent instantiation. + */ + private DataConstants() { + + } + +} diff --git a/src/org/apollo/net/codec/game/DataOrder.java b/src/org/apollo/net/codec/game/DataOrder.java new file mode 100644 index 000000000..d1cd6be8a --- /dev/null +++ b/src/org/apollo/net/codec/game/DataOrder.java @@ -0,0 +1,30 @@ +package org.apollo.net.codec.game; + +/** + * Represents the order of bytes in a {@link DataType} when + * {@code {@link DataType#getBytes()} > 1}. + * @author Graham + */ +public enum DataOrder { + + /** + * Least significant byte to most significant byte. + */ + LITTLE, + + /** + * Also known as the V1 order. + */ + MIDDLE, + + /** + * Also known as the V2 order. + */ + INVERSED_MIDDLE, + + /** + * Most significant byte to least significant byte. + */ + BIG; + +} diff --git a/src/org/apollo/net/codec/game/DataTransformation.java b/src/org/apollo/net/codec/game/DataTransformation.java new file mode 100644 index 000000000..b4f0edb6d --- /dev/null +++ b/src/org/apollo/net/codec/game/DataTransformation.java @@ -0,0 +1,30 @@ +package org.apollo.net.codec.game; + +/** + * Represents the different ways data values can be transformed. + * @author Graham + */ +public enum DataTransformation { + + /** + * No transformation is done. + */ + NONE, + + /** + * Adds 128 to the value when it is written, takes 128 from the value when + * it is read (also known as type-A). + */ + ADD, + + /** + * Negates the value (also known as type-C). + */ + NEGATE, + + /** + * Subtracts the value from 128 (also known as type-S). + */ + SUBTRACT; + +} diff --git a/src/org/apollo/net/codec/game/DataType.java b/src/org/apollo/net/codec/game/DataType.java new file mode 100644 index 000000000..52b442035 --- /dev/null +++ b/src/org/apollo/net/codec/game/DataType.java @@ -0,0 +1,55 @@ +package org.apollo.net.codec.game; + +/** + * Represents the different simple data types. + * @author Graham + */ +public enum DataType { + + /** + * A byte. + */ + BYTE(1), + + /** + * A short. + */ + SHORT(2), + + /** + * A 'tri byte' - a group of three bytes. + */ + TRI_BYTE(3), + + /** + * An integer. + */ + INT(4), + + /** + * A long. + */ + LONG(8); + + /** + * The number of bytes this type occupies. + */ + private final int bytes; + + /** + * Creates a data type. + * @param bytes The number of bytes it occupies. + */ + private DataType(int bytes) { + this.bytes = bytes; + } + + /** + * Gets the number of bytes the data type occupies. + * @return The number of bytes. + */ + public int getBytes() { + return bytes; + } + +} diff --git a/src/org/apollo/net/codec/game/GameDecoderState.java b/src/org/apollo/net/codec/game/GameDecoderState.java new file mode 100644 index 000000000..1867a6c1d --- /dev/null +++ b/src/org/apollo/net/codec/game/GameDecoderState.java @@ -0,0 +1,30 @@ +package org.apollo.net.codec.game; + +/** + * An enumeration with the different states the {@link GamePacketDecoder} can + * be in. + * @author Graham + */ +public enum GameDecoderState { + + /** + * The game opcode state waits for an encrypted opcode. It decrypts it, and + * will either set the next state to the length (if the packet is variably- + * sized) or the payload (if it is not variably-sized) state. + */ + GAME_OPCODE, + + /** + * The game length state waits for the packet length. Once it has been + * received, it sets the state to the payload state. + */ + GAME_LENGTH, + + /** + * The payload state will wait for the whole packet to be received. Then, + * it will pass a {@link GamePacket} object to Netty and reset the state + * back to the game opcode state, ready for the next packet. + */ + GAME_PAYLOAD; + +} diff --git a/src/org/apollo/net/codec/game/GameEventDecoder.java b/src/org/apollo/net/codec/game/GameEventDecoder.java new file mode 100644 index 000000000..da6fa2927 --- /dev/null +++ b/src/org/apollo/net/codec/game/GameEventDecoder.java @@ -0,0 +1,43 @@ +package org.apollo.net.codec.game; + +import org.apollo.game.event.Event; +import org.apollo.net.release.EventDecoder; +import org.apollo.net.release.Release; +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.handler.codec.oneone.OneToOneDecoder; + +/** + * A {@link OneToOneDecoder} that decodes {@link GamePacket}s into + * {@link Event}s. + * @author Graham + */ +public final class GameEventDecoder extends OneToOneDecoder { + + /** + * The current release. + */ + private final Release release; + + /** + * Creates the game event decoder with the specified release. + * @param release The release. + */ + public GameEventDecoder(Release release) { + this.release = release; + } + + @Override + protected Object decode(ChannelHandlerContext ctx, Channel c, Object msg) throws Exception { + if (msg instanceof GamePacket) { + GamePacket packet = (GamePacket) msg; + EventDecoder decoder = release.getEventDecoder(packet.getOpcode()); + if (decoder != null) { + return decoder.decode(packet); + } + return null; + } + return msg; + } + +} diff --git a/src/org/apollo/net/codec/game/GameEventEncoder.java b/src/org/apollo/net/codec/game/GameEventEncoder.java new file mode 100644 index 000000000..4e99a29ed --- /dev/null +++ b/src/org/apollo/net/codec/game/GameEventEncoder.java @@ -0,0 +1,44 @@ +package org.apollo.net.codec.game; + +import org.apollo.game.event.Event; +import org.apollo.net.release.EventEncoder; +import org.apollo.net.release.Release; +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.handler.codec.oneone.OneToOneEncoder; + +/** + * A {@link OneToOneEncoder} which encodes {@link Event}s into + * {@link GamePacket}s. + * @author Graham + */ +public final class GameEventEncoder extends OneToOneEncoder { + + /** + * The current release. + */ + private final Release release; + + /** + * Creates the game event encoder with the specified release. + * @param release The release. + */ + public GameEventEncoder(Release release) { + this.release = release; + } + + @SuppressWarnings("unchecked") + @Override + protected Object encode(ChannelHandlerContext ctx, Channel c, Object msg) throws Exception { + if (msg instanceof Event) { + Event event = (Event) msg; + EventEncoder encoder = (EventEncoder) release.getEventEncoder(event.getClass()); + if (encoder != null) { + return encoder.encode(event); + } + return null; + } + return msg; + } + +} diff --git a/src/org/apollo/net/codec/game/GamePacket.java b/src/org/apollo/net/codec/game/GamePacket.java new file mode 100644 index 000000000..ada7d7e1b --- /dev/null +++ b/src/org/apollo/net/codec/game/GamePacket.java @@ -0,0 +1,77 @@ +package org.apollo.net.codec.game; + +import org.apollo.net.meta.PacketType; +import org.jboss.netty.buffer.ChannelBuffer; + +/** + * Represents a single packet used in the in-game protocol. + * @author Graham + */ +public final class GamePacket { + + /** + * The opcode. + */ + private final int opcode; + + /** + * The packet type. + */ + private final PacketType type; + + /** + * The length. + */ + private final int length; + + /** + * The payload. + */ + private final ChannelBuffer payload; + + /** + * Creates the game packet. + * @param opcode The opcode. + * @param type The packet type. + * @param payload The payload. + */ + public GamePacket(int opcode, PacketType type, ChannelBuffer payload) { + this.opcode = opcode; + this.type = type; + this.length = payload.readableBytes(); + this.payload = payload; + } + + /** + * Gets the opcode. + * @return The opcode. + */ + public int getOpcode() { + return opcode; + } + + /** + * Gets the payload length. + * @return The payload length. + */ + public int getLength() { + return length; + } + + /** + * Gets the payload. + * @return The payload. + */ + public ChannelBuffer getPayload() { + return payload; + } + + /** + * Gets the packet type. + * @return The packet type. + */ + public PacketType getType() { + return type; + } + +} diff --git a/src/org/apollo/net/codec/game/GamePacketBuilder.java b/src/org/apollo/net/codec/game/GamePacketBuilder.java new file mode 100644 index 000000000..996e84829 --- /dev/null +++ b/src/org/apollo/net/codec/game/GamePacketBuilder.java @@ -0,0 +1,452 @@ +package org.apollo.net.codec.game; + +import org.apollo.net.NetworkConstants; +import org.apollo.net.meta.PacketType; +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.buffer.ChannelBuffers; + +/** + * A class which assists in creating a {@link GamePacket}. + * @author Graham + */ +public final class GamePacketBuilder { + + /** + * The opcode. + */ + private final int opcode; + + /** + * The {@link PacketType}. + */ + private final PacketType type; + + /** + * The buffer. + */ + private final ChannelBuffer buffer = ChannelBuffers.dynamicBuffer(); + + /** + * The current mode. + */ + private AccessMode mode = AccessMode.BYTE_ACCESS; + + /** + * The current bit index. + */ + private int bitIndex; + + /** + * Creates a raw {@link GamePacketBuilder}. + */ + public GamePacketBuilder() { + opcode = -1; + type = PacketType.RAW; + } + + /** + * Creates the {@link GamePacketBuilder} for a {@link PacketType#FIXED} + * packet with the specified opcode. + * @param opcode The opcode. + */ + public GamePacketBuilder(int opcode) { + this(opcode, PacketType.FIXED); + } + + /** + * Creates the {@link GamePacketBuilder} for the specified packet type and + * opcode. + * @param opcode The opcode. + * @param type The packet type. + */ + public GamePacketBuilder(int opcode, PacketType type) { + this.opcode = opcode; + this.type = type; + } + + /** + * Creates a {@link GamePacket} based on the current contents of this + * builder. + * @return The {@link GamePacket}. + * @throws IllegalStateException if the builder is not in byte access mode, + * or if the packet is raw. + */ + public GamePacket toGamePacket() { + if (type == PacketType.RAW) { + throw new IllegalStateException("Raw packets cannot be converted to a game packet"); + } + if (mode != AccessMode.BYTE_ACCESS) { + throw new IllegalStateException("Must be in byte access mode to convert to a packet"); + } + return new GamePacket(opcode, type, buffer); + } + + /** + * Gets the current length of the builder's buffer. + * @return The length of the buffer. + * @throws IllegalStateException if the builder is not in byte access + * mode. + */ + public int getLength() { + checkByteAccess(); + return buffer.writerIndex(); + } + + /** + * Switches this builder's mode to the byte access mode. + * @throws IllegalStateException if the builder is already in byte access + * mode. + */ + public void switchToByteAccess() { + if (mode == AccessMode.BYTE_ACCESS) { + throw new IllegalStateException("Already in byte access mode"); + } + mode = AccessMode.BYTE_ACCESS; + buffer.writerIndex((bitIndex + 7) / 8); + } + + /** + * Switches this builder's mode to the bit access mode. + * @throws IllegalStateException if the builder is already in bit access + * mode. + */ + public void switchToBitAccess() { + if (mode == AccessMode.BIT_ACCESS) { + throw new IllegalStateException("Already in bit access mode"); + } + mode = AccessMode.BIT_ACCESS; + bitIndex = buffer.writerIndex() * 8; + } + + /** + * Puts a raw builder. Both builders (this and parameter) must be in byte + * access mode. + * @param builder The builder. + */ + public void putRawBuilder(GamePacketBuilder builder) { + checkByteAccess(); + if (builder.type != PacketType.RAW) { + throw new IllegalArgumentException("Builder must be raw!"); + } + builder.checkByteAccess(); + putBytes(builder.buffer); + } + + /** + * Puts a raw builder in reverse. Both builders (this and parameter) must + * be in byte access mode. + * @param builder The builder. + */ + public void putRawBuilderReverse(GamePacketBuilder builder) { + checkByteAccess(); + if (builder.type != PacketType.RAW) { + throw new IllegalArgumentException("Builder must be raw!"); + } + builder.checkByteAccess(); + putBytesReverse(builder.buffer); + } + + /** + * Puts a standard data type with the specified value. + * @param type The data type. + * @param value The value. + * @throws IllegalStateException if this reader is not in byte access mode. + */ + public void put(DataType type, Number value) { + put(type, DataOrder.BIG, DataTransformation.NONE, value); + } + + /** + * Puts a standard data type with the specified value and byte order. + * @param type The data type. + * @param order The byte order. + * @param value The value. + * @throws IllegalStateException if this reader is not in byte access mode. + * @throws IllegalArgumentException if the combination is invalid. + */ + public void put(DataType type, DataOrder order, Number value) { + put(type, order, DataTransformation.NONE, value); + } + + /** + * Puts a standard data type with the specified value and transformation. + * @param type The type. + * @param transformation The transformation. + * @param value The value. + * @throws IllegalStateException if this reader is not in byte access mode. + * @throws IllegalArgumentException if the combination is invalid. + */ + public void put(DataType type, DataTransformation transformation, Number value) { + put(type, DataOrder.BIG, transformation, value); + } + + /** + * Puts a standard data type with the specified value, byte order and + * transformation. + * @param type The data type. + * @param order The byte order. + * @param transformation The transformation. + * @param value The value. + * @throws IllegalStateException if this reader is not in byte access mode. + * @throws IllegalArgumentException if the combination is invalid. + */ + public void put(DataType type, DataOrder order, DataTransformation transformation, Number value) { + checkByteAccess(); + long longValue = value.longValue(); + int length = type.getBytes(); + if (order == DataOrder.BIG) { + for (int i = length - 1; i >= 0; i--) { + if (i == 0 && transformation != DataTransformation.NONE) { + if (transformation == DataTransformation.ADD) { + buffer.writeByte((byte) (longValue + 128)); + } else if (transformation == DataTransformation.NEGATE) { + buffer.writeByte((byte) (-longValue)); + } else if (transformation == DataTransformation.SUBTRACT) { + buffer.writeByte((byte) (128 - longValue)); + } else { + throw new IllegalArgumentException("unknown transformation"); + } + } else { + buffer.writeByte((byte) (longValue >> (i * 8))); + } + } + } else if (order == DataOrder.LITTLE) { + for (int i = 0; i < length; i++) { + if (i == 0 && transformation != DataTransformation.NONE) { + if (transformation == DataTransformation.ADD) { + buffer.writeByte((byte) (longValue + 128)); + } else if (transformation == DataTransformation.NEGATE) { + buffer.writeByte((byte) (-longValue)); + } else if (transformation == DataTransformation.SUBTRACT) { + buffer.writeByte((byte) (128 - longValue)); + } else { + throw new IllegalArgumentException("unknown transformation"); + } + } else { + buffer.writeByte((byte) (longValue >> (i * 8))); + } + } + } else if (order == DataOrder.MIDDLE) { + if (transformation != DataTransformation.NONE) { + throw new IllegalArgumentException("middle endian cannot be transformed"); + } + if (type != DataType.INT) { + throw new IllegalArgumentException("middle endian can only be used with an integer"); + } + buffer.writeByte((byte) (longValue >> 8)); + buffer.writeByte((byte) longValue); + buffer.writeByte((byte) (longValue >> 24)); + buffer.writeByte((byte) (longValue >> 16)); + } else if (order == DataOrder.INVERSED_MIDDLE) { + if (transformation != DataTransformation.NONE) { + throw new IllegalArgumentException("inversed middle endian cannot be transformed"); + } + if (type != DataType.INT) { + throw new IllegalArgumentException("inversed middle endian can only be used with an integer"); + } + buffer.writeByte((byte) (longValue >> 16)); + buffer.writeByte((byte) (longValue >> 24)); + buffer.writeByte((byte) longValue); + buffer.writeByte((byte) (longValue >> 8)); + } else { + throw new IllegalArgumentException("unknown order"); + } + } + + /** + * Puts a string into the buffer. + * @param str The string. + */ + public void putString(String str) { + checkByteAccess(); + char[] chars = str.toCharArray(); + for (char c : chars) { + buffer.writeByte((byte) c); + } + buffer.writeByte(NetworkConstants.STRING_TERMINATOR); + } + + /** + * Puts a smart into the buffer. + * @param value The value. + */ + public void putSmart(int value) { + checkByteAccess(); + if (value < 128) { + buffer.writeByte(value); + } else { + buffer.writeShort(value); + } + } + + /** + * Puts the bytes from the specified buffer into this packet's buffer. + * @param buffer The source {@link ChannelBuffer}. + * @throws IllegalStateException if the builder is not in byte access mode. + */ + public void putBytes(ChannelBuffer buffer) { + byte[] bytes = new byte[buffer.readableBytes()]; + buffer.markReaderIndex(); + try { + buffer.readBytes(bytes); + } finally { + buffer.resetReaderIndex(); + } + putBytes(bytes); + } + + /** + * Puts the bytes from the specified buffer into this packet's buffer, in + * reverse. + * @param buffer The source {@link ChannelBuffer}. + * @throws IllegalStateException if the builder is not in byte access mode. + */ + public void putBytesReverse(ChannelBuffer buffer) { + byte[] bytes = new byte[buffer.readableBytes()]; + buffer.markReaderIndex(); + try { + buffer.readBytes(bytes); + } finally { + buffer.resetReaderIndex(); + } + putBytesReverse(bytes); + } + + /** + * Puts the specified byte array into the buffer. + * @param bytes The byte array. + * @throws IllegalStateException if the builder is not in bit access mode. + */ + public void putBytes(byte[] bytes) { + buffer.writeBytes(bytes); + } + + /** + * Puts the bytes into the buffer with the specified transformation. + * @param transformation The transformation. + * @param bytes The byte array. + *@throws IllegalStateException if the builder is not in byte access mode. + */ + public void putBytes(DataTransformation transformation, byte[] bytes) { + if (transformation == DataTransformation.NONE) { + putBytes(bytes); + } else { + for (byte b : bytes) { + put(DataType.BYTE, transformation, b); + } + } + } + + /** + * Puts the specified byte array into the buffer in reverse. + * @param bytes The byte array. + * @throws IllegalStateException if the builder is not in byte access mode. + */ + public void putBytesReverse(byte[] bytes) { + checkByteAccess(); + for (int i = bytes.length - 1; i >= 0; i--) { + buffer.writeByte(bytes[i]); + } + } + + /** + * Puts the specified byte array into the buffer in reverse with the + * specified transformation. + * @param transformation The transformation. + * @param bytes The byte array. + * @throws IllegalStateException if the builder is not in byte access mode. + */ + public void putBytesReverse(DataTransformation transformation, byte[] bytes) { + if (transformation == DataTransformation.NONE) { + putBytesReverse(bytes); + } else { + for (int i = bytes.length - 1; i >= 0; i--) { + put(DataType.BYTE, transformation, bytes[i]); + } + } + } + + /** + * Puts a single bit into the buffer. If {@code flag} is {@code true}, the + * value of the bit is {@code 1}. If {@code flag} is {@code false}, the + * value of the bit is {@code 0}. + * @param flag The flag. + * @throws IllegalStateException if the builder is not in bit access mode. + */ + public void putBit(boolean flag) { + putBit(flag ? 1 : 0); + } + + /** + * Puts a single bit into the buffer with the value {@code value}. + * @param value The value. + * @throws IllegalStateException if the builder is not in bit access mode. + */ + public void putBit(int value) { + putBits(1, value); + } + + /** + * Puts {@code numBits} into the buffer with the value {@code value}. + * @param numBits The number of bits to put into the buffer. + * @param value The value. + * @throws IllegalStateException if the builder is not in bit access mode. + * @throws IllegalArgumentException if the number of bits is not between 1 + * and 31 inclusive. + */ + public void putBits(int numBits, int value) { + if (numBits < 0 || numBits > 32) { + throw new IllegalArgumentException("Number of bits must be between 1 and 32 inclusive"); + } + + checkBitAccess(); + + int bytePos = bitIndex >> 3; + int bitOffset = 8 - (bitIndex & 7); + bitIndex += numBits; + + int requiredSpace = bytePos - buffer.writerIndex() + 1; + requiredSpace += (numBits + 7) / 8; + buffer.ensureWritableBytes(requiredSpace); + + for (; numBits > bitOffset; bitOffset = 8) { + int tmp = buffer.getByte(bytePos); + tmp &= ~DataConstants.BIT_MASK[bitOffset]; + tmp |= (value >> (numBits-bitOffset)) & DataConstants.BIT_MASK[bitOffset]; + buffer.setByte(bytePos++, tmp); + numBits -= bitOffset; + } + if (numBits == bitOffset) { + int tmp = buffer.getByte(bytePos); + tmp &= ~DataConstants.BIT_MASK[bitOffset]; + tmp |= value & DataConstants.BIT_MASK[bitOffset]; + buffer.setByte(bytePos, tmp); + } else { + int tmp = buffer.getByte(bytePos); + tmp &= ~(DataConstants.BIT_MASK[numBits] << (bitOffset - numBits)); + tmp |= (value & DataConstants.BIT_MASK[numBits]) << (bitOffset - numBits); + buffer.setByte(bytePos, tmp); + } + } + + /** + * Checks that this builder is in the byte access mode. + * @throws IllegalStateException if the builder is not in byte access mode. + */ + private void checkByteAccess() { + if (mode != AccessMode.BYTE_ACCESS) { + throw new IllegalStateException("For byte-based calls to work, the mode must be byte access"); + } + } + + /** + * Checks that this builder is in the bit access mode. + * @throws IllegalStateException if the builder is not in bit access mode. + */ + private void checkBitAccess() { + if (mode != AccessMode.BIT_ACCESS) { + throw new IllegalStateException("For bit-based calls to work, the mode must be bit access"); + } + } + +} diff --git a/src/org/apollo/net/codec/game/GamePacketDecoder.java b/src/org/apollo/net/codec/game/GamePacketDecoder.java new file mode 100644 index 000000000..078b7b78c --- /dev/null +++ b/src/org/apollo/net/codec/game/GamePacketDecoder.java @@ -0,0 +1,161 @@ +package org.apollo.net.codec.game; + +import net.burtleburtle.bob.rand.IsaacRandom; + +import org.apollo.net.meta.PacketMetaData; +import org.apollo.net.meta.PacketType; +import org.apollo.net.release.Release; +import org.apollo.util.StatefulFrameDecoder; +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.buffer.ChannelBuffers; +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelHandlerContext; + +/** + * A {@link StatefulFrameDecoder} which decodes game packets. + * @author Graham + */ +public final class GamePacketDecoder extends StatefulFrameDecoder { + + /** + * The current release. + */ + private final Release release; + + /** + * The random number generator. + */ + private final IsaacRandom random; + + /** + * The current opcode. + */ + private int opcode; + + /** + * The packet type. + */ + private PacketType type; + + /** + * The current length. + */ + private int length; + + /** + * Creates the {@link GamePacketDecoder}. + * @param random The random number generator. + * @param release The current release. + */ + public GamePacketDecoder(IsaacRandom random, Release release) { + super(GameDecoderState.GAME_OPCODE); + this.random = random; + this.release = release; + } + + @Override + protected Object decode(ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer, GameDecoderState state) throws Exception { + switch (state) { + case GAME_OPCODE: + return decodeOpcode(ctx, channel, buffer); + case GAME_LENGTH: + return decodeLength(ctx, channel, buffer); + case GAME_PAYLOAD: + return decodePayload(ctx, channel, buffer); + default: + throw new Exception("Invalid game decoder state"); + } + } + + /** + * Decodes in the opcode state. + * @param ctx The channel handler context. + * @param channel The channel. + * @param buffer The buffer. + * @return The frame, or {@code null}. + * @throws Exception if an error occurs. + */ + private Object decodeOpcode(ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) throws Exception { + if (buffer.readable()) { + int encryptedOpcode = buffer.readUnsignedByte(); + opcode = (encryptedOpcode - random.nextInt()) & 0xFF; + + PacketMetaData metaData = release.getIncomingPacketMetaData(opcode); + if (metaData == null) { + throw new Exception("Illegal opcode: " + opcode); + } + + + type = metaData.getType(); + switch (type) { + case FIXED: + length = metaData.getLength(); + if (length == 0) { + return decodeZeroLengthPacket(ctx, channel, buffer); + } else { + setState(GameDecoderState.GAME_PAYLOAD); + } + break; + case VARIABLE_BYTE: + setState(GameDecoderState.GAME_LENGTH); + break; + default: + throw new Exception("Illegal packet type: " + type); + } + } + return null; + } + + /** + * Decodes in the length state. + * @param ctx The channel handler context. + * @param channel The channel. + * @param buffer The buffer. + * @return The frame, or {@code null}. + * @throws Exception if an error occurs. + */ + private Object decodeLength(ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) throws Exception { + if (buffer.readable()) { + length = buffer.readUnsignedByte(); + if (length == 0) { + return decodeZeroLengthPacket(ctx, channel, buffer); + } else { + setState(GameDecoderState.GAME_PAYLOAD); + } + } + return null; + } + + /** + * Decodes in the payload state. + * @param ctx The channel handler context. + * @param channel The channel. + * @param buffer The buffer. + * @return The frame, or {@code null}. + * @throws Exception if an error occurs. + */ + private Object decodePayload(ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) throws Exception { + if (buffer.readableBytes() >= length) { + ChannelBuffer payload = buffer.readBytes(length); + setState(GameDecoderState.GAME_OPCODE); + return new GamePacket(opcode, type, payload); + } + return null; + } + + /** + * Decodes a zero length packet. This hackery is required as Netty will + * throw an exception if we return a frame but have read nothing! + * @param ctx The channel handler context. + * @param channel The channel. + * @param buffer The buffer. + * @return The frame, or {@code null}. + * @throws Exception if an error occurs. + */ + private Object decodeZeroLengthPacket(ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) throws Exception { + ChannelBuffer payload = ChannelBuffers.buffer(0); + setState(GameDecoderState.GAME_OPCODE); + return new GamePacket(opcode, type, payload); + } + +} diff --git a/src/org/apollo/net/codec/game/GamePacketEncoder.java b/src/org/apollo/net/codec/game/GamePacketEncoder.java new file mode 100644 index 000000000..662994cc0 --- /dev/null +++ b/src/org/apollo/net/codec/game/GamePacketEncoder.java @@ -0,0 +1,65 @@ +package org.apollo.net.codec.game; + +import net.burtleburtle.bob.rand.IsaacRandom; + +import org.apollo.net.meta.PacketType; +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.buffer.ChannelBuffers; +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.handler.codec.oneone.OneToOneEncoder; + +/** + * A {@link OneToOneEncoder} which encodes in-game packets. + * @author Graham + */ +public final class GamePacketEncoder extends OneToOneEncoder { + + /** + * The random number generator. + */ + private final IsaacRandom random; + + /** + * Creates the {@link GamePacketEncoder}. + * @param random The random number generator. + */ + public GamePacketEncoder(IsaacRandom random) { + this.random = random; + } + + @Override + protected Object encode(ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception { + if (!(msg instanceof GamePacket)) { + return msg; + } + + GamePacket packet = (GamePacket) msg; + PacketType type = packet.getType(); + int headerLength = 1; + int payloadLength = packet.getLength(); + if (type == PacketType.VARIABLE_BYTE) { + headerLength++; + if (payloadLength >= 256) { + throw new Exception("Payload too long for variable byte packet"); + } + } else if (type == PacketType.VARIABLE_SHORT) { + headerLength += 2; + if (payloadLength >= 65536) { + throw new Exception("Payload too long for variable short packet"); + } + } + + ChannelBuffer buffer = ChannelBuffers.buffer(headerLength + payloadLength); + buffer.writeByte((packet.getOpcode() + random.nextInt()) & 0xFF); + if (type == PacketType.VARIABLE_BYTE) { + buffer.writeByte(payloadLength); + } else if (type == PacketType.VARIABLE_SHORT) { + buffer.writeShort(payloadLength); + } + buffer.writeBytes(packet.getPayload()); + + return buffer; + } + +} diff --git a/src/org/apollo/net/codec/game/GamePacketReader.java b/src/org/apollo/net/codec/game/GamePacketReader.java new file mode 100644 index 000000000..10df9d572 --- /dev/null +++ b/src/org/apollo/net/codec/game/GamePacketReader.java @@ -0,0 +1,410 @@ +package org.apollo.net.codec.game; + +import org.apollo.util.ChannelBufferUtil; +import org.jboss.netty.buffer.ChannelBuffer; + +/** + * A utility class for reading {@link GamePacket}s. + * @author Graham + */ +public final class GamePacketReader { + + /** + * The buffer. + */ + private final ChannelBuffer buffer; + + /** + * The current mode. + */ + private AccessMode mode = AccessMode.BYTE_ACCESS; + + /** + * The current bit index. + */ + private int bitIndex; + + /** + * Creates the reader. + * @param packet The packet. + */ + public GamePacketReader(GamePacket packet) { + buffer = packet.getPayload(); + } + + /** + * Gets the length of this reader. + * @return The length of this reader. + */ + public int getLength() { + checkByteAccess(); + return buffer.writableBytes(); + } + + /** + * Switches this builder's mode to the byte access mode. + * @throws IllegalStateException if the builder is already in byte access + * mode. + */ + public void switchToByteAccess() { + if (mode == AccessMode.BYTE_ACCESS) { + throw new IllegalStateException("Already in byte access mode"); + } + mode = AccessMode.BYTE_ACCESS; + buffer.readerIndex((bitIndex + 7) / 8); + } + + /** + * Switches this builder's mode to the bit access mode. + * @throws IllegalStateException if the builder is already in bit access + * mode. + */ + public void switchToBitAccess() { + if (mode == AccessMode.BIT_ACCESS) { + throw new IllegalStateException("Already in bit access mode"); + } + mode = AccessMode.BIT_ACCESS; + bitIndex = buffer.readerIndex() * 8; + } + + /** + * Gets a string from the buffer. + * @return The string. + * @throws IllegalStateException if this reader is not in byte access mode. + */ + public String getString() { + checkByteAccess(); + return ChannelBufferUtil.readString(buffer); + } + + /** + * Gets a signed smart from the buffer. + * @return The smart. + * @throws IllegalStateException if this reader is not in byte access mode. + */ + public int getSignedSmart() { + checkByteAccess(); + int peek = buffer.getByte(buffer.readerIndex()); + if (peek < 128) { + return buffer.readByte() - 64; + } else { + return buffer.readShort() - 49152; + } + } + + /** + * Gets an unsigned smart from the buffer. + * @return The smart. + * @throws IllegalStateException if this reader is not in byte access mode. + */ + public int getUnsignedSmart() { + checkByteAccess(); + int peek = buffer.getByte(buffer.readerIndex()); + if (peek < 128) { + return buffer.readByte(); + } else { + return buffer.readShort() - 32768; + } + } + + /** + * Gets a signed data type from the buffer. + * @param type The data type. + * @return The value. + * @throws IllegalStateException if this reader is not in byte access mode. + */ + public long getSigned(DataType type) { + return getSigned(type, DataOrder.BIG, DataTransformation.NONE); + } + + /** + * Gets a signed data type from the buffer with the specified order. + * @param type The data type. + * @param order The byte order. + * @return The value. + * @throws IllegalStateException if this reader is not in byte access mode. + * @throws IllegalArgumentException if the combination is invalid. + */ + public long getSigned(DataType type, DataOrder order) { + return getSigned(type, order, DataTransformation.NONE); + } + + /** + * Gets a signed data type from the buffer with the specified + * transformation. + * @param type The data type. + * @param transformation The data transformation. + * @return The value. + * @throws IllegalStateException if this reader is not in byte access mode. + * @throws IllegalArgumentException if the combination is invalid. + */ + public long getSigned(DataType type, DataTransformation transformation) { + return getSigned(type, DataOrder.BIG, transformation); + } + + /** + * Gets a signed data type from the buffer with the specified order and + * transformation. + * @param type The data type. + * @param order The byte order. + * @param transformation The data transformation. + * @return The value. + * @throws IllegalStateException if this reader is not in byte access mode. + * @throws IllegalArgumentException if the combination is invalid. + */ + public long getSigned(DataType type, DataOrder order, DataTransformation transformation) { + long longValue = get(type, order, transformation); + if (type != DataType.LONG) { + int max = (int) (Math.pow(2, type.getBytes() * 8 - 1) - 1); + if (longValue > max) { + longValue -= (max + 1) * 2; + } + } + return longValue; + } + + /** + * Gets an unsigned data type from the buffer. + * @param type The data type. + * @return The value. + * @throws IllegalStateException if this reader is not in byte access mode. + */ + public long getUnsigned(DataType type) { + return getUnsigned(type, DataOrder.BIG, DataTransformation.NONE); + } + + /** + * Gets an unsigned data type from the buffer with the specified order. + * @param type The data type. + * @param order The byte order. + * @return The value. + * @throws IllegalStateException if this reader is not in byte access mode. + * @throws IllegalArgumentException if the combination is invalid. + */ + public long getUnsigned(DataType type, DataOrder order) { + return getUnsigned(type, order, DataTransformation.NONE); + } + + /** + * Gets an unsigned data type from the buffer with the specified + * transformation. + * @param type The data type. + * @param transformation The data transformation. + * @return The value. + * @throws IllegalStateException if this reader is not in byte access mode. + * @throws IllegalArgumentException if the combination is invalid. + */ + public long getUnsigned(DataType type, DataTransformation transformation) { + return getUnsigned(type, DataOrder.BIG, transformation); + } + + /** + * Gets an unsigned data type from the buffer with the specified order and + * transformation. + * @param type The data type. + * @param order The byte order. + * @param transformation The data transformation. + * @return The value. + * @throws IllegalStateException if this reader is not in byte access mode. + * @throws IllegalArgumentException if the combination is invalid. + */ + public long getUnsigned(DataType type, DataOrder order, DataTransformation transformation) { + long longValue = get(type, order, transformation); + if (type == DataType.LONG) { + throw new IllegalArgumentException("due to java restrictions, longs must be read as signed types"); + } + return longValue & 0xFFFFFFFFFFFFFFFFL; + } + + /** + * Reads a standard data type from the buffer with the specified order and + * transformation. + * @param type The data type. + * @param order The data order. + * @param transformation The data transformation. + * @return The value. + * @throws IllegalStateException if this reader is not in byte access mode. + * @throws IllegalArgumentException if the combination is invalid. + */ + private long get(DataType type, DataOrder order, DataTransformation transformation) { + checkByteAccess(); + long longValue = 0; + int length = type.getBytes(); + if (order == DataOrder.BIG) { + for (int i = length - 1; i >= 0; i--) { + if (i == 0 && transformation != DataTransformation.NONE) { + if (transformation == DataTransformation.ADD) { + longValue |= (buffer.readByte() - 128) & 0xFF; + } else if (transformation == DataTransformation.NEGATE) { + longValue |= (-buffer.readByte()) & 0xFF; + } else if (transformation == DataTransformation.SUBTRACT) { + longValue |= (128 - buffer.readByte()) & 0xFF; + } else { + throw new IllegalArgumentException("unknown transformation"); + } + } else { + longValue |= ((buffer.readByte() & 0xFF) << (i * 8)); + } + } + } else if (order == DataOrder.LITTLE) { + for (int i = 0; i < length; i++) { + if (i == 0 && transformation != DataTransformation.NONE) { + if (transformation == DataTransformation.ADD) { + longValue |= (buffer.readByte() - 128) & 0xFF; + } else if (transformation == DataTransformation.NEGATE) { + longValue |= (-buffer.readByte()) & 0xFF; + } else if (transformation == DataTransformation.SUBTRACT) { + longValue |= (128 - buffer.readByte()) & 0xFF; + } else { + throw new IllegalArgumentException("unknown transformation"); + } + } else { + longValue |= ((buffer.readByte() & 0xFF) << (i * 8)); + } + } + } else if (order == DataOrder.MIDDLE) { + if (transformation != DataTransformation.NONE) { + throw new IllegalArgumentException("middle endian cannot be transformed"); + } + if (type != DataType.INT) { + throw new IllegalArgumentException("middle endian can only be used with an integer"); + } + longValue |= (buffer.readByte() & 0xFF) << 8; + longValue |= buffer.readByte() & 0xFF; + longValue |= (buffer.readByte() & 0xFF) << 24; + longValue |= (buffer.readByte() & 0xFF) << 16; + } else if (order == DataOrder.INVERSED_MIDDLE) { + if (transformation != DataTransformation.NONE) { + throw new IllegalArgumentException("inversed middle endian cannot be transformed"); + } + if (type != DataType.INT) { + throw new IllegalArgumentException("inversed middle endian can only be used with an integer"); + } + longValue |= (buffer.readByte() & 0xFF) << 16; + longValue |= (buffer.readByte() & 0xFF) << 24; + longValue |= buffer.readByte() & 0xFF; + longValue |= (buffer.readByte() & 0xFF) << 8; + } else { + throw new IllegalArgumentException("unknown order"); + } + return longValue; + } + + /** + * Gets bytes. + * @param bytes The target byte array. + * @throws IllegalStateException if this reader is not in byte access mode. + */ + public void getBytes(byte[] bytes) { + checkByteAccess(); + for (int i = 0; i < bytes.length; i++) { + bytes[i] = buffer.readByte(); + } + } + + /** + * Gets bytes with the specified transformation. + * @param transformation The transformation. + * @param bytes The target byte array. + * @throws IllegalStateException if this reader is not in byte access mode. + */ + public void getBytes(DataTransformation transformation, byte[] bytes) { + if (transformation == DataTransformation.NONE) { + getBytesReverse(bytes); + } else { + for (int i = 0; i < bytes.length; i++) { + bytes[i] = (byte) getSigned(DataType.BYTE, transformation); + } + } + } + + /** + * Gets bytes in reverse. + * @param bytes The target byte array. + * @throws IllegalStateException if this reader is not in byte access mode. + */ + public void getBytesReverse(byte[] bytes) { + checkByteAccess(); + for (int i = bytes.length - 1; i >= 0; i--) { + bytes[i] = buffer.readByte(); + } + } + + /** + * Gets bytes in reverse with the specified transformation. + * @param transformation The transformation. + * @param bytes The target byte array. + * @throws IllegalStateException if this reader is not in byte access mode. + */ + public void getBytesReverse(DataTransformation transformation, byte[] bytes) { + if (transformation == DataTransformation.NONE) { + getBytesReverse(bytes); + } else { + for (int i = bytes.length - 1; i >= 0; i--) { + bytes[i] = (byte) getSigned(DataType.BYTE, transformation); + } + } + } + + /** + * Checks that this reader is in the byte access mode. + * @throws IllegalStateException if the reader is not in byte access mode. + */ + private void checkByteAccess() { + if (mode != AccessMode.BYTE_ACCESS) { + throw new IllegalStateException("For byte-based calls to work, the mode must be byte access"); + } + } + + /** + * Checks that this reader is in the bit access mode. + * @throws IllegalStateException if the reader is not in bit access mode. + */ + private void checkBitAccess() { + if (mode != AccessMode.BIT_ACCESS) { + throw new IllegalStateException("For bit-based calls to work, the mode must be bit access"); + } + } + + /** + * Gets a bit from the buffer. + * @return The value. + * @throws IllegalStateException if the reader is not in bit access mode. + */ + public int getBit() { + return getBits(1); + } + + /** + * Gets {@code numBits} from the buffer. + * @param numBits The number of bits. + * @return The value. + * @throws IllegalStateException if the reader is not in bit access mode. + * @throws IllegalArgumentException if the number of bits is not between 1 + * and 31 inclusive. + */ + public int getBits(int numBits) { + if (numBits < 0 || numBits > 32) { + throw new IllegalArgumentException("Number of bits must be between 1 and 32 inclusive"); + } + + checkBitAccess(); + + int bytePos = bitIndex >> 3; + int bitOffset = 8 - (bitIndex & 7); + int value = 0; + bitIndex +=numBits; + + for (; numBits > bitOffset; bitOffset = 8) { + value += (buffer.getByte(bytePos++) & DataConstants.BIT_MASK[bitOffset]) << numBits - bitOffset; + numBits -= bitOffset; + } + if (numBits == bitOffset) { + value += buffer.getByte(bytePos) & DataConstants.BIT_MASK[bitOffset]; + } else { + value += buffer.getByte(bytePos) >> bitOffset - numBits & DataConstants.BIT_MASK[numBits]; + } + return value; + } + +} diff --git a/src/org/apollo/net/codec/game/package-info.java b/src/org/apollo/net/codec/game/package-info.java new file mode 100644 index 000000000..0f1a7fef4 --- /dev/null +++ b/src/org/apollo/net/codec/game/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains codecs for the game protocol. + */ +package org.apollo.net.codec.game; diff --git a/src/org/apollo/net/codec/handshake/HandshakeConstants.java b/src/org/apollo/net/codec/handshake/HandshakeConstants.java new file mode 100644 index 000000000..2dc236454 --- /dev/null +++ b/src/org/apollo/net/codec/handshake/HandshakeConstants.java @@ -0,0 +1,26 @@ +package org.apollo.net.codec.handshake; + +/** + * Holds handshake-related constants. + * @author Graham + */ +public final class HandshakeConstants { + + /** + * The id of the game service. + */ + public static final int SERVICE_GAME = 14; + + /** + * The id of the update service. + */ + public static final int SERVICE_UPDATE = 15; + + /** + * Default private constructor to prevent instantiation by other classes. + */ + private HandshakeConstants() { + + } + +} diff --git a/src/org/apollo/net/codec/handshake/HandshakeDecoder.java b/src/org/apollo/net/codec/handshake/HandshakeDecoder.java new file mode 100644 index 000000000..20dd40f10 --- /dev/null +++ b/src/org/apollo/net/codec/handshake/HandshakeDecoder.java @@ -0,0 +1,63 @@ +package org.apollo.net.codec.handshake; + +import org.apollo.net.codec.login.LoginDecoder; +import org.apollo.net.codec.login.LoginEncoder; +import org.apollo.net.codec.update.UpdateDecoder; +import org.apollo.net.codec.update.UpdateEncoder; +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.buffer.ChannelBuffers; +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.handler.codec.frame.FrameDecoder; + +/** + * A {@link FrameDecoder} which decodes the handshake and makes changes to the + * pipeline as appropriate for the selected service. + * @author Graham + */ +public final class HandshakeDecoder extends FrameDecoder { + + /** + * Creates the handshake frame decoder. + */ + public HandshakeDecoder() { + super(true); + } + + @Override + protected Object decode(ChannelHandlerContext ctx, Channel channel, + ChannelBuffer buffer) throws Exception { + if (buffer.readable()) { + int id = buffer.readUnsignedByte(); + + switch (id) { + case HandshakeConstants.SERVICE_GAME: + ctx.getPipeline().addFirst("loginEncoder", new LoginEncoder()); + ctx.getPipeline().addBefore("handler", "loginDecoder", new LoginDecoder()); + break; + case HandshakeConstants.SERVICE_UPDATE: + ctx.getPipeline().addFirst("updateEncoder", new UpdateEncoder()); + ctx.getPipeline().addBefore("handler", "updateDecoder", new UpdateDecoder()); + ChannelBuffer buf = ChannelBuffers.buffer(8); + buf.writeLong(0); + channel.write(buf); // TODO should it be here? + break; + default: + throw new Exception("Invalid service id"); + } + + ctx.getPipeline().remove(this); + + HandshakeMessage message = new HandshakeMessage(id); + + if (buffer.readable()) { + return new Object[] { message, buffer.readBytes(buffer.readableBytes()) }; + } else { + return message; + } + + } + return null; + } + +} diff --git a/src/org/apollo/net/codec/handshake/HandshakeMessage.java b/src/org/apollo/net/codec/handshake/HandshakeMessage.java new file mode 100644 index 000000000..f299005a8 --- /dev/null +++ b/src/org/apollo/net/codec/handshake/HandshakeMessage.java @@ -0,0 +1,30 @@ +package org.apollo.net.codec.handshake; + +/** + * A handshake message in the service handshake protocol. + * @author Graham + */ +public final class HandshakeMessage { + + /** + * The service id. + */ + private final int serviceId; + + /** + * Creates the handshake message. + * @param serviceId The service id. + */ + public HandshakeMessage(int serviceId) { + this.serviceId = serviceId; + } + + /** + * Gets the service id. + * @return The service id. + */ + public int getServiceId() { + return serviceId; + } + +} diff --git a/src/org/apollo/net/codec/handshake/package-info.java b/src/org/apollo/net/codec/handshake/package-info.java new file mode 100644 index 000000000..33e2f54cc --- /dev/null +++ b/src/org/apollo/net/codec/handshake/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains codecs for the handshake protocol. + */ +package org.apollo.net.codec.handshake; diff --git a/src/org/apollo/net/codec/jaggrab/JagGrabRequest.java b/src/org/apollo/net/codec/jaggrab/JagGrabRequest.java new file mode 100644 index 000000000..069f3cb31 --- /dev/null +++ b/src/org/apollo/net/codec/jaggrab/JagGrabRequest.java @@ -0,0 +1,30 @@ +package org.apollo.net.codec.jaggrab; + +/** + * Represents the request for a single file using the JAGGRAB protocol. + * @author Graham + */ +public final class JagGrabRequest { + + /** + * The path to the file. + */ + private final String filePath; + + /** + * Creates the request. + * @param filePath The file path. + */ + public JagGrabRequest(String filePath) { + this.filePath = filePath; + } + + /** + * Gets the file path. + * @return The file path. + */ + public String getFilePath() { + return filePath; + } + +} diff --git a/src/org/apollo/net/codec/jaggrab/JagGrabRequestDecoder.java b/src/org/apollo/net/codec/jaggrab/JagGrabRequestDecoder.java new file mode 100644 index 000000000..ad5741267 --- /dev/null +++ b/src/org/apollo/net/codec/jaggrab/JagGrabRequestDecoder.java @@ -0,0 +1,27 @@ +package org.apollo.net.codec.jaggrab; + +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.handler.codec.oneone.OneToOneDecoder; + +/** + * A {@link OneToOneDecoder} for the JAGGRAB protocol. + * @author Graham + */ +public final class JagGrabRequestDecoder extends OneToOneDecoder { + + @Override + protected Object decode(ChannelHandlerContext ctx, Channel c, Object msg) throws Exception { + if (msg instanceof String) { + String str = ((String) msg); + if (str.startsWith("JAGGRAB /")) { + String filePath = str.substring(8).trim(); + return new JagGrabRequest(filePath); + } else { + throw new Exception("corrupted request line"); + } + } + return msg; + } + +} diff --git a/src/org/apollo/net/codec/jaggrab/JagGrabResponse.java b/src/org/apollo/net/codec/jaggrab/JagGrabResponse.java new file mode 100644 index 000000000..72560bbde --- /dev/null +++ b/src/org/apollo/net/codec/jaggrab/JagGrabResponse.java @@ -0,0 +1,32 @@ +package org.apollo.net.codec.jaggrab; + +import org.jboss.netty.buffer.ChannelBuffer; + +/** + * Represents a single JAGGRAB reponse. + * @author Graham + */ +public final class JagGrabResponse { + + /** + * The file data. + */ + private final ChannelBuffer fileData; + + /** + * Creates the response. + * @param fileData The file data. + */ + public JagGrabResponse(ChannelBuffer fileData) { + this.fileData = fileData; + } + + /** + * Gets the file data. + * @return The file data. + */ + public ChannelBuffer getFileData() { + return fileData; + } + +} diff --git a/src/org/apollo/net/codec/jaggrab/JagGrabResponseEncoder.java b/src/org/apollo/net/codec/jaggrab/JagGrabResponseEncoder.java new file mode 100644 index 000000000..8850ed483 --- /dev/null +++ b/src/org/apollo/net/codec/jaggrab/JagGrabResponseEncoder.java @@ -0,0 +1,22 @@ +package org.apollo.net.codec.jaggrab; + +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.handler.codec.oneone.OneToOneEncoder; + +/** + * A {@link OneToOneEncoder} for the JAGGRAB protocol. + * @author Graham + */ +public final class JagGrabResponseEncoder extends OneToOneEncoder { + + @Override + protected Object encode(ChannelHandlerContext ctx, Channel c, Object msg) throws Exception { + if (msg instanceof JagGrabResponse) { + JagGrabResponse resp = (JagGrabResponse) msg; + return resp.getFileData(); + } + return msg; + } + +} diff --git a/src/org/apollo/net/codec/jaggrab/package-info.java b/src/org/apollo/net/codec/jaggrab/package-info.java new file mode 100644 index 000000000..b97f667be --- /dev/null +++ b/src/org/apollo/net/codec/jaggrab/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains codecs for the JAGGRAB protocol. + */ +package org.apollo.net.codec.jaggrab; diff --git a/src/org/apollo/net/codec/login/LoginConstants.java b/src/org/apollo/net/codec/login/LoginConstants.java new file mode 100644 index 000000000..0e86c1660 --- /dev/null +++ b/src/org/apollo/net/codec/login/LoginConstants.java @@ -0,0 +1,126 @@ +package org.apollo.net.codec.login; + +/** + * Holds login-related constants. + * @author Graham + */ +public final class LoginConstants { + + /** + * Standard login type id. + */ + public static final int TYPE_STANDARD = 16; + + /** + * Reconnection login type id. + */ + public static final int TYPE_RECONNECTION = 18; + + /** + * Exchange data login status. + */ + public static final int STATUS_EXCHANGE_DATA = 0; + + /** + * Delay for 2 seconds login status. + */ + public static final int STATUS_DELAY = 1; + + /** + * OK login status. + */ + public static final int STATUS_OK = 2; + + /** + * Invalid credentials login status. + */ + public static final int STATUS_INVALID_CREDENTIALS = 3; + + /** + * Account disabled login status. + */ + public static final int STATUS_ACCOUNT_DISABLED = 4; + + /** + * Account online login status. + */ + public static final int STATUS_ACCOUNT_ONLINE = 5; + + /** + * Game updated login status. + */ + public static final int STATUS_GAME_UPDATED = 6; + + /** + * Server full login status. + */ + public static final int STATUS_SERVER_FULL = 7; + + /** + * Login server offline login status. + */ + public static final int STATUS_LOGIN_SERVER_OFFLINE = 8; + + /** + * Too many connections login status. + */ + public static final int STATUS_TOO_MANY_CONNECTIONS = 9; + + /** + * Bad session id login status. + */ + public static final int STATUS_BAD_SESSION_ID = 10; + + /** + * Login server rejected session login status. + */ + public static final int STATUS_LOGIN_SERVER_REJECTED_SESSION = 11; + + /** + * Members account required login status. + */ + public static final int STATUS_MEMBERS_ACCOUNT_REQUIRED = 12; + + /** + * Could not complete login status. + */ + public static final int STATUS_COULD_NOT_COMPLETE = 13; + + /** + * Server updating login status. + */ + public static final int STATUS_UPDATING = 14; + + /** + * Reconnection OK login status. + */ + public static final int STATUS_RECONNECTION_OK = 15; + + /** + * Too many login attempts login status. + */ + public static final int STATUS_TOO_MANY_LOGINS = 16; + + /** + * Standing in members area on free world status. + */ + public static final int STATUS_IN_MEMBERS_AREA = 17; + + /** + * Invalid login server status. + */ + public static final int STATUS_INVALID_LOGIN_SERVER = 20; + + /** + * Profile transfer login status. + */ + public static final int STATUS_PROFILE_TRANSFER = 21; + + /** + * Default private constructor to prevent instantiation. + */ + private LoginConstants() { + + } + +} diff --git a/src/org/apollo/net/codec/login/LoginDecoder.java b/src/org/apollo/net/codec/login/LoginDecoder.java new file mode 100644 index 000000000..d538d697d --- /dev/null +++ b/src/org/apollo/net/codec/login/LoginDecoder.java @@ -0,0 +1,204 @@ +package org.apollo.net.codec.login; + +import java.security.SecureRandom; + +import net.burtleburtle.bob.rand.IsaacRandom; + +import org.apollo.fs.FileSystemConstants; +import org.apollo.security.IsaacRandomPair; +import org.apollo.security.PlayerCredentials; +import org.apollo.util.ChannelBufferUtil; +import org.apollo.util.StatefulFrameDecoder; +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.buffer.ChannelBuffers; +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelHandlerContext; + +/** + * A {@link StatefulFrameDecoder} which decodes the login request frames. + * @author Graham + */ +public final class LoginDecoder extends StatefulFrameDecoder { + + /** + * The secure random number generator. + */ + private static final SecureRandom random = new SecureRandom(); + + /** + * The username hash. + */ + private int usernameHash; + + /** + * The server-side session key. + */ + private long serverSeed; + + /** + * The reconnecting flag. + */ + private boolean reconnecting; + + /** + * The login packet length. + */ + private int loginLength; + + /** + * Creates the login decoder with the default initial state. + */ + public LoginDecoder() { + super(LoginDecoderState.LOGIN_HANDSHAKE, true); + } + + @Override + protected Object decode(ChannelHandlerContext ctx, Channel channel, + ChannelBuffer buffer, LoginDecoderState state) throws Exception { + switch (state) { + case LOGIN_HANDSHAKE: + return decodeHandshake(ctx, channel, buffer); + case LOGIN_HEADER: + return decodeHeader(ctx, channel, buffer); + case LOGIN_PAYLOAD: + return decodePayload(ctx, channel, buffer); + default: + throw new Exception("Invalid login decoder state"); + } + } + + /** + * Decodes in the handshake state. + * @param ctx The channel handler context. + * @param channel The channel. + * @param buffer The buffer. + * @return The frame, or {@code null}. + * @throws Exception if an error occurs. + */ + private Object decodeHandshake(ChannelHandlerContext ctx, Channel channel, + ChannelBuffer buffer) throws Exception { + if (buffer.readable()) { + usernameHash = buffer.readUnsignedByte(); + serverSeed = random.nextLong(); + + ChannelBuffer resp = ChannelBuffers.buffer(17); + resp.writeByte(LoginConstants.STATUS_EXCHANGE_DATA); + resp.writeLong(0); + resp.writeLong(serverSeed); + channel.write(resp); + + setState(LoginDecoderState.LOGIN_HEADER); + } + return null; + } + + /** + * Decodes in the header state. + * @param ctx The channel handler context. + * @param channel The channel. + * @param buffer The buffer. + * @return The frame, or {@code null}. + * @throws Exception if an error occurs. + */ + private Object decodeHeader(ChannelHandlerContext ctx, Channel channel, + ChannelBuffer buffer) throws Exception { + if (buffer.readableBytes() >= 2) { + int loginType = buffer.readUnsignedByte(); + + if (loginType != LoginConstants.TYPE_STANDARD && loginType != LoginConstants.TYPE_RECONNECTION) { + throw new Exception("Invalid login type"); + } + + reconnecting = loginType == LoginConstants.TYPE_RECONNECTION; + + loginLength = buffer.readUnsignedByte(); + + setState(LoginDecoderState.LOGIN_PAYLOAD); + } + return null; + } + + /** + * Decodes in the payload state. + * @param ctx The channel handler context. + * @param channel The channel. + * @param buffer The buffer. + * @return The frame, or {@code null}. + * @throws Exception if an error occurs. + */ + private Object decodePayload(ChannelHandlerContext ctx, Channel channel, + ChannelBuffer buffer) throws Exception { + if (buffer.readableBytes() >= loginLength) { + ChannelBuffer payload = buffer.readBytes(loginLength); + if (payload.readUnsignedByte() != 0xFF) { + throw new Exception("Invalid magic id"); + } + + int releaseNumber = payload.readUnsignedShort(); + + int lowMemoryFlag = payload.readUnsignedByte(); + if (lowMemoryFlag != 0 && lowMemoryFlag != 1) { + throw new Exception("Invalid value for low memory flag"); + } + + boolean lowMemory = lowMemoryFlag == 1; + + int[] archiveCrcs = new int[FileSystemConstants.ARCHIVE_COUNT]; + for (int i = 0; i < 9; i++) { + archiveCrcs[i] = payload.readInt(); + } + + int securePayloadLength = payload.readUnsignedByte(); + if (securePayloadLength != (loginLength - 41)) { + throw new Exception("Secure payload length mismatch"); + } + + ChannelBuffer securePayload = payload.readBytes(securePayloadLength); + + int secureId = securePayload.readUnsignedByte(); + if (secureId != 10) { + throw new Exception("Invalid secure payload id"); + } + + long clientSeed = securePayload.readLong(); + long reportedServerSeed = securePayload.readLong(); + if (reportedServerSeed != serverSeed) { + throw new Exception("Server seed mismatch"); + } + + int uid = securePayload.readInt(); + + String username = ChannelBufferUtil.readString(securePayload); + String password = ChannelBufferUtil.readString(securePayload); + + if (username.length() > 12 || password.length() > 20) { + throw new Exception("Username or password too long"); + } + + int[] seed = new int[4]; + seed[0] = (int) (clientSeed >> 32); + seed[1] = (int) clientSeed; + seed[2] = (int) (serverSeed >> 32); + seed[3] = (int) serverSeed; + + IsaacRandom decodingRandom = new IsaacRandom(seed); + for (int i = 0; i < seed.length; i++) { + seed[i] += 50; + } + IsaacRandom encodingRandom = new IsaacRandom(seed); + + PlayerCredentials credentials = new PlayerCredentials(username, password, usernameHash, uid); + IsaacRandomPair randomPair = new IsaacRandomPair(encodingRandom, decodingRandom); + + LoginRequest req = new LoginRequest(credentials, randomPair, reconnecting, lowMemory, releaseNumber, archiveCrcs); + + if (buffer.readable()) { + return new Object[] { req, buffer.readBytes(buffer.readableBytes()) }; + } else { + return req; + } + } + return null; + } + +} diff --git a/src/org/apollo/net/codec/login/LoginDecoderState.java b/src/org/apollo/net/codec/login/LoginDecoderState.java new file mode 100644 index 000000000..77e3974b3 --- /dev/null +++ b/src/org/apollo/net/codec/login/LoginDecoderState.java @@ -0,0 +1,30 @@ +package org.apollo.net.codec.login; + +/** + * An enumeration with the different states the {@link LoginDecoder} can + * be in. + * @author Graham + */ +public enum LoginDecoderState { + + /** + * The login handshake state will wait for the username hash to be + * received. Once it is, a server session key will be sent to the client + * and the state will be set to the login header state. + */ + LOGIN_HANDSHAKE, + + /** + * The login header state will wait for the login type and payload length + * to be received. These are saved, and then the state will be set to the + * login payload state. + */ + LOGIN_HEADER, + + /** + * The login payload state will wait for all login information (such as + * client release number, username and password). + */ + LOGIN_PAYLOAD; + +} diff --git a/src/org/apollo/net/codec/login/LoginEncoder.java b/src/org/apollo/net/codec/login/LoginEncoder.java new file mode 100644 index 000000000..54936c399 --- /dev/null +++ b/src/org/apollo/net/codec/login/LoginEncoder.java @@ -0,0 +1,34 @@ +package org.apollo.net.codec.login; + +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.buffer.ChannelBuffers; +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.handler.codec.oneone.OneToOneEncoder; + +/** + * A class which encodes login response messsages. + * @author Graham + */ +public final class LoginEncoder extends OneToOneEncoder { + + @Override + protected Object encode(ChannelHandlerContext ctx, Channel channel, + Object message) throws Exception { + if (!(message instanceof LoginResponse)) { + return message; + } + + LoginResponse response = (LoginResponse) message; + + ChannelBuffer buffer = ChannelBuffers.buffer(3); + buffer.writeByte(response.getStatus()); + if (response.getStatus() == LoginConstants.STATUS_OK) { + buffer.writeByte(response.getRights()); + buffer.writeByte(response.isFlagged() ? 1 : 0); + } + + return buffer; + } + +} diff --git a/src/org/apollo/net/codec/login/LoginRequest.java b/src/org/apollo/net/codec/login/LoginRequest.java new file mode 100644 index 000000000..61eb0dd15 --- /dev/null +++ b/src/org/apollo/net/codec/login/LoginRequest.java @@ -0,0 +1,109 @@ +package org.apollo.net.codec.login; + +import org.apollo.security.IsaacRandomPair; +import org.apollo.security.PlayerCredentials; + +/** + * Represents a login request. + * @author Graham + */ +public final class LoginRequest { + + /** + * The player's credentials. + */ + private final PlayerCredentials credentials; + + /** + * The pair of random number generators. + */ + private final IsaacRandomPair randomPair; + + /** + * The low memory flag. + */ + private final boolean lowMemory; + + /** + * The reconnecting flag. + */ + private final boolean reconnecting; + + /** + * The release number. + */ + private final int releaseNumber; + + /** + * The archive CRCs. + */ + private final int[] archiveCrcs; + + /** + * Creates a login request. + * @param credentials The player credentials. + * @param randomPair The pair of random number generators. + * @param lowMemory The low memory flag. + * @param reconnecting The reconnecting flag. + * @param releaseNumber The release number. + * @param archiveCrcs The archive CRCs. + */ + public LoginRequest(PlayerCredentials credentials, + IsaacRandomPair randomPair, boolean lowMemory, boolean reconnecting, int releaseNumber, int[] archiveCrcs) { + this.credentials = credentials; + this.randomPair = randomPair; + this.lowMemory = lowMemory; + this.reconnecting = reconnecting; + this.releaseNumber = releaseNumber; + this.archiveCrcs = archiveCrcs; + } + + /** + * Gets the player's credentials. + * @return The player's credentials. + */ + public PlayerCredentials getCredentials() { + return credentials; + } + + /** + * Gets the pair of random number generators. + * @return The pair of random number generators. + */ + public IsaacRandomPair getRandomPair() { + return randomPair; + } + + /** + * Checks if this client is in low memory mode. + * @return {@code true} if so, {@code false} if not. + */ + public boolean isLowMemory() { + return lowMemory; + } + + /** + * Checks if this client is reconnecting. + * @return {@code true} if so, {@code false} if not. + */ + public boolean isReconnecting() { + return reconnecting; + } + + /** + * Gets the release number. + * @return The release number. + */ + public int getReleaseNumber() { + return releaseNumber; + } + + /** + * Gets the archive CRCs. + * @return The array of archive CRCs. + */ + public int[] getArchiveCrcs() { + return archiveCrcs; + } + +} diff --git a/src/org/apollo/net/codec/login/LoginResponse.java b/src/org/apollo/net/codec/login/LoginResponse.java new file mode 100644 index 000000000..80c7715a6 --- /dev/null +++ b/src/org/apollo/net/codec/login/LoginResponse.java @@ -0,0 +1,60 @@ +package org.apollo.net.codec.login; + +/** + * Represents a login response. + * @author Graham + */ +public final class LoginResponse { + + /** + * The login status. + */ + private final int status; + + /** + * The flagged flag. + */ + private final boolean flagged; + + /** + * The rights level. + */ + private final int rights; + + /** + * Creates the login response. + * @param status The login status. + * @param rights The rights level. + * @param flagged The flagged flag. + */ + public LoginResponse(int status, int rights, boolean flagged) { + this.status = status; + this.rights = rights; + this.flagged = flagged; + } + + /** + * Gets the status. + * @return The status. + */ + public int getStatus() { + return status; + } + + /** + * Checks if the player should be flagged. + * @return The flagged flag. + */ + public boolean isFlagged() { + return flagged; + } + + /** + * Gets the rights level. + * @return The rights level. + */ + public int getRights() { + return rights; + } + +} diff --git a/src/org/apollo/net/codec/login/package-info.java b/src/org/apollo/net/codec/login/package-info.java new file mode 100644 index 000000000..ff8f46db5 --- /dev/null +++ b/src/org/apollo/net/codec/login/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains codecs for the login protocol. + */ +package org.apollo.net.codec.login; diff --git a/src/org/apollo/net/codec/update/OnDemandRequest.java b/src/org/apollo/net/codec/update/OnDemandRequest.java new file mode 100644 index 000000000..140646896 --- /dev/null +++ b/src/org/apollo/net/codec/update/OnDemandRequest.java @@ -0,0 +1,129 @@ +package org.apollo.net.codec.update; + +import org.apollo.fs.FileDescriptor; + +/** + * Represents a single 'on-demand' request. + * @author Graham + */ +public final class OnDemandRequest implements Comparable { + + /** + * An enumeration containing the different request priorities. + * @author Graham + */ + public enum Priority { + + /** + * High priority - used in-game when data is required immediately but + * has not yet been received. + */ + HIGH(0), + + /** + * Medium priority - used while loading the 'bare minimum' required to + * run the game. + */ + MEDIUM(1), + + /** + * Low priority - used when a file is not required urgently. The client + * login screen says "loading extra files.." when low priority loading + * is being performed. + */ + LOW(2); + + /** + * Converts the integer value to a priority. + * @param v The integer value. + * @return The priority. + * @throws IllegalArgumentException if the value is outside of the + * range 1-3 inclusive. + */ + public static Priority valueOf(int v) { + switch (v) { + case 0: + return HIGH; + case 1: + return MEDIUM; + case 2: + return LOW; + default: + throw new IllegalArgumentException("priority out of range"); + } + } + + /** + * The integer value. + */ + private final int intValue; + + /** + * Creates a priority. + * @param intValue The integer value. + */ + private Priority(int intValue) { + this.intValue = intValue; + } + + /** + * Converts the priority to an integer. + * @return The integer value. + */ + public int toInteger() { + return intValue; + } + + } + + /** + * The file descriptor. + */ + private final FileDescriptor fileDescriptor; + + /** + * The request priority. + */ + private final Priority priority; + + /** + * Creates the 'on-demand' request. + * @param fileDescriptor The file descriptor. + * @param priority The priority. + */ + public OnDemandRequest(FileDescriptor fileDescriptor, Priority priority) { + this.fileDescriptor = fileDescriptor; + this.priority = priority; + } + + /** + * Gets the file descriptor. + * @return The file descriptor. + */ + public FileDescriptor getFileDescriptor() { + return fileDescriptor; + } + + /** + * Gets the priority. + * @return The priority. + */ + public Priority getPriority() { + return priority; + } + + @Override + public int compareTo(OnDemandRequest o) { + int thisPriority = priority.toInteger(); + int otherPriority = o.priority.toInteger(); + + if (thisPriority < otherPriority) { + return 1; + } else if (thisPriority == otherPriority) { + return 0; + } else { + return -1; + } + } + +} diff --git a/src/org/apollo/net/codec/update/OnDemandResponse.java b/src/org/apollo/net/codec/update/OnDemandResponse.java new file mode 100644 index 000000000..0f2fdc0b0 --- /dev/null +++ b/src/org/apollo/net/codec/update/OnDemandResponse.java @@ -0,0 +1,78 @@ +package org.apollo.net.codec.update; + +import org.apollo.fs.FileDescriptor; +import org.jboss.netty.buffer.ChannelBuffer; + +/** + * Represents a single 'on-demand' response. + * @author Graham + */ +public final class OnDemandResponse { + + /** + * The file descriptor. + */ + private final FileDescriptor fileDescriptor; + + /** + * The file size. + */ + private final int fileSize; + + /** + * The chunk id. + */ + private final int chunkId; + + /** + * The chunk data. + */ + private final ChannelBuffer chunkData; + + /** + * Creates the 'on-demand' response. + * @param fileDescriptor The file descriptor. + * @param fileSize The file size. + * @param chunkId The chunk id. + * @param chunkData The chunk data. + */ + public OnDemandResponse(FileDescriptor fileDescriptor, int fileSize, int chunkId, ChannelBuffer chunkData) { + this.fileDescriptor = fileDescriptor; + this.fileSize = fileSize; + this.chunkId = chunkId; + this.chunkData = chunkData; + } + + /** + * Gets the file descriptor. + * @return The file descriptor. + */ + public FileDescriptor getFileDescriptor() { + return fileDescriptor; + } + + /** + * Gets the file size. + * @return The file size. + */ + public int getFileSize() { + return fileSize; + } + + /** + * Gets the chunk id. + * @return The chunk id. + */ + public int getChunkId() { + return chunkId; + } + + /** + * Gets the chunk data. + * @return The chunk data. + */ + public ChannelBuffer getChunkData() { + return chunkData; + } + +} diff --git a/src/org/apollo/net/codec/update/UpdateDecoder.java b/src/org/apollo/net/codec/update/UpdateDecoder.java new file mode 100644 index 000000000..7e22e5ea7 --- /dev/null +++ b/src/org/apollo/net/codec/update/UpdateDecoder.java @@ -0,0 +1,31 @@ +package org.apollo.net.codec.update; + +import org.apollo.fs.FileDescriptor; +import org.apollo.net.codec.update.OnDemandRequest.Priority; +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.handler.codec.frame.FrameDecoder; + +/** + * A {@link FrameDecoder} for the 'on-demand' protocol. + * @author Graham + */ +public final class UpdateDecoder extends FrameDecoder { + + @Override + protected Object decode(ChannelHandlerContext ctx, Channel c, ChannelBuffer buf) throws Exception { + if (buf.readableBytes() >= 4) { + int type = buf.readUnsignedByte() + 1; + int file = buf.readUnsignedShort(); + int priority = buf.readUnsignedByte(); + + FileDescriptor desc = new FileDescriptor(type, file); + Priority p = Priority.valueOf(priority); + + return new OnDemandRequest(desc, p); + } + return null; + } + +} diff --git a/src/org/apollo/net/codec/update/UpdateEncoder.java b/src/org/apollo/net/codec/update/UpdateEncoder.java new file mode 100644 index 000000000..6d4261de2 --- /dev/null +++ b/src/org/apollo/net/codec/update/UpdateEncoder.java @@ -0,0 +1,38 @@ +package org.apollo.net.codec.update; + +import org.apollo.fs.FileDescriptor; +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.buffer.ChannelBuffers; +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.handler.codec.oneone.OneToOneEncoder; + +/** + * A {@link OneToOneEncoder} for the 'on-demand' protocol. + * @author Graham + */ +public final class UpdateEncoder extends OneToOneEncoder { + + @Override + protected Object encode(ChannelHandlerContext ctx, Channel c, Object msg) throws Exception { + if (msg instanceof OnDemandResponse) { + OnDemandResponse resp = (OnDemandResponse) msg; + + FileDescriptor fileDescriptor = resp.getFileDescriptor(); + int fileSize = resp.getFileSize(); + int chunkId = resp.getChunkId(); + ChannelBuffer chunkData = resp.getChunkData(); + + ChannelBuffer buf = ChannelBuffers.buffer(6 + chunkData.readableBytes()); + buf.writeByte(fileDescriptor.getType() - 1); + buf.writeShort(fileDescriptor.getFile()); + buf.writeShort(fileSize); + buf.writeByte(chunkId); + buf.writeBytes(chunkData); + + return buf; + } + return msg; + } + +} diff --git a/src/org/apollo/net/codec/update/package-info.java b/src/org/apollo/net/codec/update/package-info.java new file mode 100644 index 000000000..354193ef5 --- /dev/null +++ b/src/org/apollo/net/codec/update/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains codecs for the ondemand (update) protocol. + */ +package org.apollo.net.codec.update; diff --git a/src/org/apollo/net/meta/PacketMetaData.java b/src/org/apollo/net/meta/PacketMetaData.java new file mode 100644 index 000000000..38b145746 --- /dev/null +++ b/src/org/apollo/net/meta/PacketMetaData.java @@ -0,0 +1,82 @@ +package org.apollo.net.meta; + +/** + * A class which contains meta data for a single type of packet. + * @author Graham + */ +public final class PacketMetaData { + + /** + * Creates a {@link PacketMetaData} object for a fixed-length packet. + * @param length The length of the packet. + * @return The {@link PacketMetaData} object. + * @throws IllegalArgumentException if length is less than 0. + */ + public static PacketMetaData createFixed(int length) { + if (length < 0) { + throw new IllegalArgumentException(); + } + return new PacketMetaData(PacketType.FIXED, length); + } + + /** + * Creates a {@link PacketMetaData} object for a variable byte length + * packet. + * @return The {@link PacketMetaData} object. + */ + public static PacketMetaData createVariableByte() { + return new PacketMetaData(PacketType.VARIABLE_BYTE, 0); + } + + /** + * Creates a {@link PacketMetaData} object for a variable short length + * packet. + * @return The {@link PacketMetaData} object. + */ + public static PacketMetaData createVariableShort() { + return new PacketMetaData(PacketType.VARIABLE_SHORT, 0); + } + + /** + * The type of packet. + */ + private final PacketType type; + + /** + * The length of this packet. + */ + private final int length; + + /** + * Creates the packet meta data object. This should not be called directy. + * Use the {@link #createFixed(int)}, {@link #createVariableByte()} and + * {@link #createVariableShort()} methods instead! + * @param type The type of packet. + * @param length The length of the packet. + */ + private PacketMetaData(PacketType type, int length) { + this.type = type; + this.length = length; + } + + /** + * Gets the type of packet. + * @return The type of packet. + */ + public PacketType getType() { + return type; + } + + /** + * Gets the length of this packet. + * @return The length of this packet. + * @throws IllegalStateException if the packet is not a fixed-size packet. + */ + public int getLength() { + if (type != PacketType.FIXED) { + throw new IllegalStateException(); + } + return length; + } + +} diff --git a/src/org/apollo/net/meta/PacketMetaDataGroup.java b/src/org/apollo/net/meta/PacketMetaDataGroup.java new file mode 100644 index 000000000..8a4a63a97 --- /dev/null +++ b/src/org/apollo/net/meta/PacketMetaDataGroup.java @@ -0,0 +1,66 @@ +package org.apollo.net.meta; + +/** + * A class which contains a group of {@link PacketMetaData} objects. + * @author Graham + */ +public final class PacketMetaDataGroup { + + /** + * Creates a {@link PacketMetaDataGroup} from the packet length array. + * @param lengthArray The packet length array. + * @return The {@link PacketMetaDataGroup} object. + * @throws IllegalArgumentException if the array length is not 256 or if + * there is an element in the array with a value below -3. + */ + public static PacketMetaDataGroup createFromArray(int[] lengthArray) { + if (lengthArray.length != 256) { + throw new IllegalArgumentException(); + } + PacketMetaDataGroup grp = new PacketMetaDataGroup(); + for (int i = 0; i < lengthArray.length; i++) { + int length = lengthArray[i]; + PacketMetaData metaData = null; + if (length < -3) { + throw new IllegalArgumentException(); + } else if (length == -2) { + metaData = PacketMetaData.createVariableShort(); + } else if (length == -1) { + metaData = PacketMetaData.createVariableByte(); + } else { + metaData = PacketMetaData.createFixed(length); + } + grp.packets[i] = metaData; + } + return grp; + } + + /** + * The array of {@link PacketMetaData} objects. + */ + private final PacketMetaData[] packets = new PacketMetaData[256]; + + /** + * This constructor should not be called directly. Use the + * {@link #createFromArray(int[])} method instead. + */ + private PacketMetaDataGroup() { + + } + + /** + * Gets the meta data for the specified packet. + * @param opcode The opcode of the packet. + * @return The {@link PacketMetaData}, or {@code null} if the packet does + * not exist. + * @throws IllegalArgumentException if the opcoe is not in the range 0 to + * 255. + */ + public PacketMetaData getMetaData(int opcode) { + if (opcode < 0 || opcode >= packets.length) { + throw new IllegalArgumentException(); + } + return packets[opcode]; + } + +} diff --git a/src/org/apollo/net/meta/PacketType.java b/src/org/apollo/net/meta/PacketType.java new file mode 100644 index 000000000..fc8b4267e --- /dev/null +++ b/src/org/apollo/net/meta/PacketType.java @@ -0,0 +1,30 @@ +package org.apollo.net.meta; + +/** + * An enumeration which contains the different types of packets. + * @author Graham + */ +public enum PacketType { + + /** + * A packet with no header. + */ + RAW, + + /** + * A packet where the length is known by both the client and server + * already. + */ + FIXED, + + /** + * A packet where the length is sent to its destination with it as a byte. + */ + VARIABLE_BYTE, + + /** + * A packet where the length is sent to its destination with it as a short. + */ + VARIABLE_SHORT; + +} diff --git a/src/org/apollo/net/meta/package-info.java b/src/org/apollo/net/meta/package-info.java new file mode 100644 index 000000000..2a3561909 --- /dev/null +++ b/src/org/apollo/net/meta/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains classes which contain meta data about the protocol/various packets. + */ +package org.apollo.net.meta; diff --git a/src/org/apollo/net/package-info.java b/src/org/apollo/net/package-info.java new file mode 100644 index 000000000..3939c715b --- /dev/null +++ b/src/org/apollo/net/package-info.java @@ -0,0 +1,5 @@ +/** + * Contains classes related to networking. Many of these extend Netty's set of + * classes - such as the pipeline factory, handler and codecs. + */ +package org.apollo.net; diff --git a/src/org/apollo/net/release/EventDecoder.java b/src/org/apollo/net/release/EventDecoder.java new file mode 100644 index 000000000..a68f0d11e --- /dev/null +++ b/src/org/apollo/net/release/EventDecoder.java @@ -0,0 +1,21 @@ +package org.apollo.net.release; + +import org.apollo.game.event.Event; +import org.apollo.net.codec.game.GamePacket; + +/** + * An {@link EventDecoder} decodes a {@link GamePacket} into an {@link Event} + * object which can be processed by the server. + * @author Graham + * @param The type of {@link Event}. + */ +public abstract class EventDecoder { + + /** + * Decodes the specified packet into an event. + * @param packet The packet. + * @return The event. + */ + public abstract E decode(GamePacket packet); + +} diff --git a/src/org/apollo/net/release/EventEncoder.java b/src/org/apollo/net/release/EventEncoder.java new file mode 100644 index 000000000..e8291b24c --- /dev/null +++ b/src/org/apollo/net/release/EventEncoder.java @@ -0,0 +1,21 @@ +package org.apollo.net.release; + +import org.apollo.game.event.Event; +import org.apollo.net.codec.game.GamePacket; + +/** + * An {@link EventEncoder} encodes {@link Event} objects into + * {@link GamePacket}s which can be sent over the network. + * @author Graham + * @param The type of {@link Event}. + */ +public abstract class EventEncoder { + + /** + * Encodes the specified event into a packet. + * @param event The event. + * @return The packet. + */ + public abstract GamePacket encode(E event); + +} diff --git a/src/org/apollo/net/release/Release.java b/src/org/apollo/net/release/Release.java new file mode 100644 index 000000000..8a5b57e66 --- /dev/null +++ b/src/org/apollo/net/release/Release.java @@ -0,0 +1,107 @@ +package org.apollo.net.release; + +import java.util.HashMap; +import java.util.Map; + +import org.apollo.game.event.Event; +import org.apollo.net.meta.PacketMetaData; +import org.apollo.net.meta.PacketMetaDataGroup; + +/** + * A {@link Release} is a distinct client version, for example 317 is a common + * release used in server emulators. + * @author Graham + */ +public abstract class Release { + + /** + * The release number, e.g. {@code 317}. + */ + private final int releaseNumber; + + /** + * The decoders. + */ + private final EventDecoder[] decoders = new EventDecoder[256]; + + /** + * The encoders. + */ + private final Map, EventEncoder> encoders = new HashMap, EventEncoder>(); + + /** + * The incoming packet meta data. + */ + private final PacketMetaDataGroup incomingPacketMetaData; + + /** + * Creates the release. + * @param releaseNumber The release number. + * @param incomingPacketMetaData The incoming packet meta data. + */ + public Release(int releaseNumber, PacketMetaDataGroup incomingPacketMetaData) { + this.releaseNumber = releaseNumber; + this.incomingPacketMetaData = incomingPacketMetaData; + } + + /** + * Gets the release number. + * @return The release number. + */ + public final int getReleaseNumber() { + return releaseNumber; + } + + /** + * Registers a {@link EventDecoder} for the specified opcode. + * @param opcode The opcode, between 0 and 255 inclusive. + * @param decoder The {@link EventDecoder}. + */ + public final void register(int opcode, EventDecoder decoder) { + if (opcode < 0 || opcode >= decoders.length) { + throw new IndexOutOfBoundsException(); + } + decoders[opcode] = decoder; + } + + /** + * Registers a {@link EventEncoder} for the specified event type. + * @param type The event type. + * @param encoder The {@link EventEncoder}. + */ + public final void register(Class type, EventEncoder encoder) { + encoders.put(type, encoder); + } + + /** + * Gets meta data for the specified incoming packet. + * @param opcode The opcode of the incoming packet. + * @return The {@link PacketMetaData} object. + */ + public final PacketMetaData getIncomingPacketMetaData(int opcode) { + return incomingPacketMetaData.getMetaData(opcode); + } + + /** + * Gets the {@link EventDecoder} for the specified opcode. + * @param opcode The opcode. + * @return The {@link EventDecoder}. + */ + public final EventDecoder getEventDecoder(int opcode) { + if (opcode < 0 || opcode >= decoders.length) { + throw new IndexOutOfBoundsException(); + } + return decoders[opcode]; + } + + /** + * Gets an {@link EventEncoder} for the specified event type. + * @param type The type of event. + * @return The {@link EventEncoder}. + */ + @SuppressWarnings("unchecked") + public EventEncoder getEventEncoder(Class type) { + return (EventEncoder) encoders.get(type); + } + +} diff --git a/src/org/apollo/net/release/package-info.java b/src/org/apollo/net/release/package-info.java new file mode 100644 index 000000000..1e34440c7 --- /dev/null +++ b/src/org/apollo/net/release/package-info.java @@ -0,0 +1,5 @@ +/** + * Contains abstract classes which should be extended by a particular release, + * allowing for portability between various protocol and client releases. + */ +package org.apollo.net.release; diff --git a/src/org/apollo/net/release/r317/ButtonEventDecoder.java b/src/org/apollo/net/release/r317/ButtonEventDecoder.java new file mode 100644 index 000000000..8ab8a1e0b --- /dev/null +++ b/src/org/apollo/net/release/r317/ButtonEventDecoder.java @@ -0,0 +1,22 @@ +package org.apollo.net.release.r317; + +import org.apollo.game.event.impl.ButtonEvent; +import org.apollo.net.codec.game.DataType; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketReader; +import org.apollo.net.release.EventDecoder; + +/** + * An {@link EventDecoder} for the {@link ButtonEvent}. + * @author Graham + */ +public final class ButtonEventDecoder extends EventDecoder { + + @Override + public ButtonEvent decode(GamePacket packet) { + GamePacketReader reader = new GamePacketReader(packet); + int interfaceId = (int) reader.getUnsigned(DataType.SHORT); + return new ButtonEvent(interfaceId); + } + +} diff --git a/src/org/apollo/net/release/r317/CharacterDesignEventDecoder.java b/src/org/apollo/net/release/r317/CharacterDesignEventDecoder.java new file mode 100644 index 000000000..ff9f70a9a --- /dev/null +++ b/src/org/apollo/net/release/r317/CharacterDesignEventDecoder.java @@ -0,0 +1,38 @@ +package org.apollo.net.release.r317; + +import org.apollo.game.event.impl.CharacterDesignEvent; +import org.apollo.game.model.Appearance; +import org.apollo.game.model.Gender; +import org.apollo.net.codec.game.DataType; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketReader; +import org.apollo.net.release.EventDecoder; + +/** + * An {@link EventDecoder} for the {@link CharacterDesignEvent}. + * @author Graham + */ +public final class CharacterDesignEventDecoder extends EventDecoder { + + @Override + public CharacterDesignEvent decode(GamePacket packet) { + GamePacketReader reader = new GamePacketReader(packet); + + int genderIntValue = (int) reader.getUnsigned(DataType.BYTE); + + int[] style = new int[7]; + for (int i = 0; i < style.length; i++) { + style[i] = (int) reader.getUnsigned(DataType.BYTE); + } + + int[] color = new int[5]; + for (int i = 0; i < color.length; i++) { + color[i] = (int) reader.getUnsigned(DataType.BYTE); + } + + Gender gender = genderIntValue == Gender.MALE.toInteger() ? Gender.MALE : Gender.FEMALE; + + return new CharacterDesignEvent(new Appearance(gender, style, color)); + } + +} diff --git a/src/org/apollo/net/release/r317/ChatEventDecoder.java b/src/org/apollo/net/release/r317/ChatEventDecoder.java new file mode 100644 index 000000000..072ae517f --- /dev/null +++ b/src/org/apollo/net/release/r317/ChatEventDecoder.java @@ -0,0 +1,38 @@ +package org.apollo.net.release.r317; + +import org.apollo.game.event.impl.ChatEvent; +import org.apollo.net.codec.game.DataTransformation; +import org.apollo.net.codec.game.DataType; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketReader; +import org.apollo.net.release.EventDecoder; +import org.apollo.util.TextUtil; + +/** + * An {@link EventDecoder} for the {@link ChatEvent}. + * @author Graham + */ +public final class ChatEventDecoder extends EventDecoder { + + @Override + public ChatEvent decode(GamePacket packet) { + GamePacketReader reader = new GamePacketReader(packet); + + int effects = (int) reader.getUnsigned(DataType.BYTE, DataTransformation.ADD); + int color = (int) reader.getUnsigned(DataType.BYTE, DataTransformation.ADD); + int length = packet.getLength() - 2; + + byte[] originalCompressed = new byte[length]; + reader.getBytesReverse(DataTransformation.ADD, originalCompressed); + + String uncompressed = TextUtil.uncompress(originalCompressed, length); + uncompressed = TextUtil.filterInvalidCharacters(uncompressed); + uncompressed = TextUtil.capitalize(uncompressed); + + byte[] recompressed = new byte[length]; + TextUtil.compress(uncompressed, recompressed); // in case invalid data gets sent, this effectively verifies it + + return new ChatEvent(uncompressed, recompressed, color, effects); + } + +} diff --git a/src/org/apollo/net/release/r317/CloseInterfaceEventEncoder.java b/src/org/apollo/net/release/r317/CloseInterfaceEventEncoder.java new file mode 100644 index 000000000..7b2c62a25 --- /dev/null +++ b/src/org/apollo/net/release/r317/CloseInterfaceEventEncoder.java @@ -0,0 +1,20 @@ +package org.apollo.net.release.r317; + +import org.apollo.game.event.impl.CloseInterfaceEvent; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketBuilder; +import org.apollo.net.release.EventEncoder; + +/** + * An {@link EventEncoder} for the {@link CloseInterfaceEvent}. + * @author Graham + */ +public final class CloseInterfaceEventEncoder extends EventEncoder { + + @Override + public GamePacket encode(CloseInterfaceEvent event) { + GamePacketBuilder builder = new GamePacketBuilder(219); + return builder.toGamePacket(); + } + +} diff --git a/src/org/apollo/net/release/r317/ClosedInterfaceEventDecoder.java b/src/org/apollo/net/release/r317/ClosedInterfaceEventDecoder.java new file mode 100644 index 000000000..63374e281 --- /dev/null +++ b/src/org/apollo/net/release/r317/ClosedInterfaceEventDecoder.java @@ -0,0 +1,18 @@ +package org.apollo.net.release.r317; + +import org.apollo.game.event.impl.ClosedInterfaceEvent; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.release.EventDecoder; + +/** + * An {@link EventDecoder} for the {@link ClosedInterfaceEvent}. + * @author Graham + */ +public final class ClosedInterfaceEventDecoder extends EventDecoder { + + @Override + public ClosedInterfaceEvent decode(GamePacket packet) { + return new ClosedInterfaceEvent(); + } + +} diff --git a/src/org/apollo/net/release/r317/CommandEventDecoder.java b/src/org/apollo/net/release/r317/CommandEventDecoder.java new file mode 100644 index 000000000..2b37bf07b --- /dev/null +++ b/src/org/apollo/net/release/r317/CommandEventDecoder.java @@ -0,0 +1,20 @@ +package org.apollo.net.release.r317; + +import org.apollo.game.event.impl.CommandEvent; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketReader; +import org.apollo.net.release.EventDecoder; + +/** + * An {@link EventDecoder} for the {@link CommandEvent}. + * @author Graham + */ +public final class CommandEventDecoder extends EventDecoder { + + @Override + public CommandEvent decode(GamePacket packet) { + GamePacketReader reader = new GamePacketReader(packet); + return new CommandEvent(reader.getString()); + } + +} diff --git a/src/org/apollo/net/release/r317/EnterAmountEventEncoder.java b/src/org/apollo/net/release/r317/EnterAmountEventEncoder.java new file mode 100644 index 000000000..94e5d9743 --- /dev/null +++ b/src/org/apollo/net/release/r317/EnterAmountEventEncoder.java @@ -0,0 +1,20 @@ +package org.apollo.net.release.r317; + +import org.apollo.game.event.impl.EnterAmountEvent; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.meta.PacketType; +import org.apollo.net.release.EventEncoder; +import org.jboss.netty.buffer.ChannelBuffers; + +/** + * An {@link EventEncoder} for the {@link EnterAmountEvent}. + * @author Graham + */ +public final class EnterAmountEventEncoder extends EventEncoder { + + @Override + public GamePacket encode(EnterAmountEvent event) { + return new GamePacket(27, PacketType.FIXED, ChannelBuffers.EMPTY_BUFFER); + } + +} diff --git a/src/org/apollo/net/release/r317/EnteredAmountEventDecoder.java b/src/org/apollo/net/release/r317/EnteredAmountEventDecoder.java new file mode 100644 index 000000000..fcc7754c7 --- /dev/null +++ b/src/org/apollo/net/release/r317/EnteredAmountEventDecoder.java @@ -0,0 +1,22 @@ +package org.apollo.net.release.r317; + +import org.apollo.game.event.impl.EnteredAmountEvent; +import org.apollo.net.codec.game.DataType; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketReader; +import org.apollo.net.release.EventDecoder; + +/** + * An {@link EventDecoder} for the {@link EnteredAmountEvent}. + * @author Graham + */ +public final class EnteredAmountEventDecoder extends EventDecoder { + + @Override + public EnteredAmountEvent decode(GamePacket packet) { + GamePacketReader reader = new GamePacketReader(packet); + int amount = (int) reader.getUnsigned(DataType.INT); + return new EnteredAmountEvent(amount); + } + +} diff --git a/src/org/apollo/net/release/r317/EquipEventDecoder.java b/src/org/apollo/net/release/r317/EquipEventDecoder.java new file mode 100644 index 000000000..5bd78739f --- /dev/null +++ b/src/org/apollo/net/release/r317/EquipEventDecoder.java @@ -0,0 +1,25 @@ +package org.apollo.net.release.r317; + +import org.apollo.game.event.impl.EquipEvent; +import org.apollo.net.codec.game.DataTransformation; +import org.apollo.net.codec.game.DataType; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketReader; +import org.apollo.net.release.EventDecoder; + +/** + * An {@link EventDecoder} for the {@link EquipEvent}. + * @author Graham + */ +public final class EquipEventDecoder extends EventDecoder { + + @Override + public EquipEvent decode(GamePacket packet) { + GamePacketReader reader = new GamePacketReader(packet); + int id = (int) reader.getUnsigned(DataType.SHORT); + int slot = (int) reader.getUnsigned(DataType.SHORT, DataTransformation.ADD); + int interfaceId = (int) reader.getUnsigned(DataType.SHORT, DataTransformation.ADD); + return new EquipEvent(interfaceId, id, slot); + } + +} diff --git a/src/org/apollo/net/release/r317/FifthItemActionEventDecoder.java b/src/org/apollo/net/release/r317/FifthItemActionEventDecoder.java new file mode 100644 index 000000000..54b89dc70 --- /dev/null +++ b/src/org/apollo/net/release/r317/FifthItemActionEventDecoder.java @@ -0,0 +1,27 @@ +package org.apollo.net.release.r317; + +import org.apollo.game.event.impl.FifthItemActionEvent; +import org.apollo.game.event.impl.FourthItemActionEvent; +import org.apollo.net.codec.game.DataOrder; +import org.apollo.net.codec.game.DataTransformation; +import org.apollo.net.codec.game.DataType; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketReader; +import org.apollo.net.release.EventDecoder; + +/** + * An {@link EventDecoder} for the {@link FourthItemActionEvent}. + * @author Graham + */ +public final class FifthItemActionEventDecoder extends EventDecoder { + + @Override + public FifthItemActionEvent decode(GamePacket packet) { + GamePacketReader reader = new GamePacketReader(packet); + int slot = (int) reader.getUnsigned(DataType.SHORT, DataOrder.LITTLE, DataTransformation.ADD); + int interfaceId = (int) reader.getUnsigned(DataType.SHORT, DataTransformation.ADD); + int id = (int) reader.getUnsigned(DataType.SHORT, DataOrder.LITTLE); + return new FifthItemActionEvent(interfaceId, id, slot); + } + +} diff --git a/src/org/apollo/net/release/r317/FirstItemActionEventDecoder.java b/src/org/apollo/net/release/r317/FirstItemActionEventDecoder.java new file mode 100644 index 000000000..ffbe31546 --- /dev/null +++ b/src/org/apollo/net/release/r317/FirstItemActionEventDecoder.java @@ -0,0 +1,25 @@ +package org.apollo.net.release.r317; + +import org.apollo.game.event.impl.FirstItemActionEvent; +import org.apollo.net.codec.game.DataTransformation; +import org.apollo.net.codec.game.DataType; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketReader; +import org.apollo.net.release.EventDecoder; + +/** + * An {@link EventDecoder} for the {@link FirstItemActionEvent}. + * @author Graham + */ +public final class FirstItemActionEventDecoder extends EventDecoder { + + @Override + public FirstItemActionEvent decode(GamePacket packet) { + GamePacketReader reader = new GamePacketReader(packet); + int interfaceId = (int) reader.getUnsigned(DataType.SHORT, DataTransformation.ADD); + int slot = (int) reader.getUnsigned(DataType.SHORT, DataTransformation.ADD); + int id = (int) reader.getUnsigned(DataType.SHORT, DataTransformation.ADD); + return new FirstItemActionEvent(interfaceId, id, slot); + } + +} diff --git a/src/org/apollo/net/release/r317/FirstObjectActionEventDecoder.java b/src/org/apollo/net/release/r317/FirstObjectActionEventDecoder.java new file mode 100644 index 000000000..28b82480e --- /dev/null +++ b/src/org/apollo/net/release/r317/FirstObjectActionEventDecoder.java @@ -0,0 +1,27 @@ +package org.apollo.net.release.r317; + +import org.apollo.game.event.impl.FirstObjectActionEvent; +import org.apollo.game.model.Position; +import org.apollo.net.codec.game.DataOrder; +import org.apollo.net.codec.game.DataTransformation; +import org.apollo.net.codec.game.DataType; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketReader; +import org.apollo.net.release.EventDecoder; + +/** + * An {@link EventDecoder} for the {@link FirstObjectActionEvent}. + * @author Graham + */ +public final class FirstObjectActionEventDecoder extends EventDecoder { + + @Override + public FirstObjectActionEvent decode(GamePacket packet) { + GamePacketReader reader = new GamePacketReader(packet); + int x = (int) reader.getUnsigned(DataType.SHORT, DataOrder.LITTLE, DataTransformation.ADD); + int y = (int) reader.getUnsigned(DataType.SHORT); + int id = (int) reader.getUnsigned(DataType.SHORT, DataTransformation.ADD); + return new FirstObjectActionEvent(id, new Position(x, y)); + } + +} diff --git a/src/org/apollo/net/release/r317/FourthItemActionEventDecoder.java b/src/org/apollo/net/release/r317/FourthItemActionEventDecoder.java new file mode 100644 index 000000000..5b6b15942 --- /dev/null +++ b/src/org/apollo/net/release/r317/FourthItemActionEventDecoder.java @@ -0,0 +1,25 @@ +package org.apollo.net.release.r317; + +import org.apollo.game.event.impl.FourthItemActionEvent; +import org.apollo.net.codec.game.DataTransformation; +import org.apollo.net.codec.game.DataType; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketReader; +import org.apollo.net.release.EventDecoder; + +/** + * An {@link EventDecoder} for the {@link FourthItemActionEvent}. + * @author Graham + */ +public final class FourthItemActionEventDecoder extends EventDecoder { + + @Override + public FourthItemActionEvent decode(GamePacket packet) { + GamePacketReader reader = new GamePacketReader(packet); + int slot = (int) reader.getUnsigned(DataType.SHORT, DataTransformation.ADD); + int interfaceId = (int) reader.getUnsigned(DataType.SHORT); + int id = (int) reader.getUnsigned(DataType.SHORT, DataTransformation.ADD); + return new FourthItemActionEvent(interfaceId, id, slot); + } + +} diff --git a/src/org/apollo/net/release/r317/IdAssignmentEventEncoder.java b/src/org/apollo/net/release/r317/IdAssignmentEventEncoder.java new file mode 100644 index 000000000..faf3bbdb2 --- /dev/null +++ b/src/org/apollo/net/release/r317/IdAssignmentEventEncoder.java @@ -0,0 +1,25 @@ +package org.apollo.net.release.r317; + +import org.apollo.game.event.impl.IdAssignmentEvent; +import org.apollo.net.codec.game.DataOrder; +import org.apollo.net.codec.game.DataTransformation; +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.EventEncoder; + +/** + * An {@link EventEncoder} for the {@link IdAssignmentEvent}. + * @author Graham + */ +public final class IdAssignmentEventEncoder extends EventEncoder { + + @Override + public GamePacket encode(IdAssignmentEvent event) { + GamePacketBuilder builder = new GamePacketBuilder(249); + builder.put(DataType.BYTE, DataTransformation.ADD, event.isMembers() ? 1 : 0); + builder.put(DataType.SHORT, DataOrder.LITTLE, DataTransformation.ADD, event.getId()); + return builder.toGamePacket(); + } + +} diff --git a/src/org/apollo/net/release/r317/KeepAliveEventDecoder.java b/src/org/apollo/net/release/r317/KeepAliveEventDecoder.java new file mode 100644 index 000000000..29ee57d57 --- /dev/null +++ b/src/org/apollo/net/release/r317/KeepAliveEventDecoder.java @@ -0,0 +1,18 @@ +package org.apollo.net.release.r317; + +import org.apollo.game.event.impl.KeepAliveEvent; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.release.EventDecoder; + +/** + * A {@link EventDecoder} for the {@link KeepAliveEvent}. + * @author Graham + */ +public final class KeepAliveEventDecoder extends EventDecoder { + + @Override + public KeepAliveEvent decode(GamePacket packet) { + return new KeepAliveEvent(); + } + +} diff --git a/src/org/apollo/net/release/r317/LogoutEventEncoder.java b/src/org/apollo/net/release/r317/LogoutEventEncoder.java new file mode 100644 index 000000000..370f9f1dc --- /dev/null +++ b/src/org/apollo/net/release/r317/LogoutEventEncoder.java @@ -0,0 +1,20 @@ +package org.apollo.net.release.r317; + +import org.apollo.game.event.impl.LogoutEvent; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketBuilder; +import org.apollo.net.release.EventEncoder; + +/** + * An {@link EventEncoder} for the {@link LogoutEvent}. + * @author Graham + */ +public final class LogoutEventEncoder extends EventEncoder { + + @Override + public GamePacket encode(LogoutEvent event) { + GamePacketBuilder builder = new GamePacketBuilder(109); + return builder.toGamePacket(); + } + +} diff --git a/src/org/apollo/net/release/r317/OpenInterfaceEventEncoder.java b/src/org/apollo/net/release/r317/OpenInterfaceEventEncoder.java new file mode 100644 index 000000000..8cf625039 --- /dev/null +++ b/src/org/apollo/net/release/r317/OpenInterfaceEventEncoder.java @@ -0,0 +1,22 @@ +package org.apollo.net.release.r317; + +import org.apollo.game.event.impl.OpenInterfaceEvent; +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.EventEncoder; + +/** + * An {@link EventEncoder} for the {@link OpenInterfaceEvent}. + * @author Graham + */ +public final class OpenInterfaceEventEncoder extends EventEncoder { + + @Override + public GamePacket encode(OpenInterfaceEvent event) { + GamePacketBuilder builder = new GamePacketBuilder(97); + builder.put(DataType.SHORT, event.getId()); + return builder.toGamePacket(); + } + +} diff --git a/src/org/apollo/net/release/r317/OpenInterfaceSidebarEventEncoder.java b/src/org/apollo/net/release/r317/OpenInterfaceSidebarEventEncoder.java new file mode 100644 index 000000000..08e022c3d --- /dev/null +++ b/src/org/apollo/net/release/r317/OpenInterfaceSidebarEventEncoder.java @@ -0,0 +1,24 @@ +package org.apollo.net.release.r317; + +import org.apollo.game.event.impl.OpenInterfaceSidebarEvent; +import org.apollo.net.codec.game.DataTransformation; +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.EventEncoder; + +/** + * An {@link EventEncoder} for the {@link OpenInterfaceSidebarEvent}. + * @author Graham + */ +public final class OpenInterfaceSidebarEventEncoder extends EventEncoder { + + @Override + public GamePacket encode(OpenInterfaceSidebarEvent event) { + GamePacketBuilder builder = new GamePacketBuilder(248); + builder.put(DataType.SHORT, DataTransformation.ADD, event.getInterfaceId()); + builder.put(DataType.SHORT, event.getSidebarId()); + return builder.toGamePacket(); + } + +} diff --git a/src/org/apollo/net/release/r317/PlayerSynchronizationEventEncoder.java b/src/org/apollo/net/release/r317/PlayerSynchronizationEventEncoder.java new file mode 100644 index 000000000..401e35b9c --- /dev/null +++ b/src/org/apollo/net/release/r317/PlayerSynchronizationEventEncoder.java @@ -0,0 +1,353 @@ +package org.apollo.net.release.r317; + +import org.apollo.game.event.impl.PlayerSynchronizationEvent; +import org.apollo.game.model.Animation; +import org.apollo.game.model.Appearance; +import org.apollo.game.model.Direction; +import org.apollo.game.model.EquipmentConstants; +import org.apollo.game.model.Gender; +import org.apollo.game.model.Graphic; +import org.apollo.game.model.Inventory; +import org.apollo.game.model.Item; +import org.apollo.game.model.Position; +import org.apollo.game.model.def.EquipmentDefinition; +import org.apollo.game.sync.block.AnimationBlock; +import org.apollo.game.sync.block.AppearanceBlock; +import org.apollo.game.sync.block.ChatBlock; +import org.apollo.game.sync.block.GraphicBlock; +import org.apollo.game.sync.block.SynchronizationBlockSet; +import org.apollo.game.sync.block.TurnToPositionBlock; +import org.apollo.game.sync.seg.AddCharacterSegment; +import org.apollo.game.sync.seg.MovementSegment; +import org.apollo.game.sync.seg.SegmentType; +import org.apollo.game.sync.seg.SynchronizationSegment; +import org.apollo.game.sync.seg.TeleportSegment; +import org.apollo.net.codec.game.DataOrder; +import org.apollo.net.codec.game.DataTransformation; +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.meta.PacketType; +import org.apollo.net.release.EventEncoder; + +/** + * An {@link EventEncoder} for the {@link PlayerSynchronizationEvent}. + * @author Graham + */ +public final class PlayerSynchronizationEventEncoder extends EventEncoder { + + @Override + public GamePacket encode(PlayerSynchronizationEvent event) { + GamePacketBuilder builder = new GamePacketBuilder(81, PacketType.VARIABLE_SHORT); + builder.switchToBitAccess(); + + GamePacketBuilder blockBuilder = new GamePacketBuilder(); + + putMovementUpdate(event.getSegment(), event, builder); + putBlocks(event.getSegment(), blockBuilder); + + builder.putBits(8, event.getLocalPlayers()); + + for (SynchronizationSegment segment : event.getSegments()) { + SegmentType type = segment.getType(); + if (type == SegmentType.REMOVE_CHARACTER) { + putRemoveCharacterUpdate(builder); + } else if (type == SegmentType.ADD_CHARACTER) { + putAddCharacterUpdate((AddCharacterSegment) segment, event, builder); + putBlocks(segment, blockBuilder); + } else { + putMovementUpdate(segment, event, builder); + putBlocks(segment, blockBuilder); + } + } + + if (blockBuilder.getLength() > 0) { + builder.putBits(11, 2047); + builder.switchToByteAccess(); + builder.putRawBuilder(blockBuilder); + } else { + builder.switchToByteAccess(); + } + + return builder.toGamePacket(); + } + + /** + * Puts a remove character update. + * @param builder The builder. + */ + private void putRemoveCharacterUpdate(GamePacketBuilder builder) { + builder.putBits(1, 1); + builder.putBits(2, 3); + } + + /** + * Puts an add character update. + * @param seg The segment. + * @param event The event. + * @param builder The builder. + */ + private void putAddCharacterUpdate(AddCharacterSegment seg, PlayerSynchronizationEvent event, GamePacketBuilder builder) { + boolean updateRequired = seg.getBlockSet().size() > 0; + Position player = event.getPosition(); + Position other = seg.getPosition(); + builder.putBits(11, seg.getIndex()); + builder.putBits(1, updateRequired ? 1 : 0); + builder.putBits(1, 1); // discard walking queue? + builder.putBits(5, other.getY() - player.getY()); + builder.putBits(5, other.getX() - player.getX()); + } + + /** + * Puts a movement update for the specified segment. + * @param seg The segment. + * @param event The event. + * @param builder The builder. + */ + private void putMovementUpdate(SynchronizationSegment seg, PlayerSynchronizationEvent event, GamePacketBuilder builder) { + boolean updateRequired = seg.getBlockSet().size() > 0; + if (seg.getType() == SegmentType.TELEPORT) { + Position pos = ((TeleportSegment) seg).getDestination(); + builder.putBits(1, 1); + builder.putBits(2, 3); + builder.putBits(2, pos.getHeight()); + builder.putBits(1, event.hasRegionChanged() ? 0 : 1); + builder.putBits(1, updateRequired ? 1 : 0); + builder.putBits(7, pos.getLocalY(event.getLastKnownRegion())); + builder.putBits(7, pos.getLocalX(event.getLastKnownRegion())); + } else if (seg.getType() == SegmentType.RUN) { + Direction[] directions = ((MovementSegment) seg).getDirections(); + builder.putBits(1, 1); + builder.putBits(2, 2); + builder.putBits(3, directions[0].toInteger()); + builder.putBits(3, directions[1].toInteger()); + builder.putBits(1, updateRequired ? 1 : 0); + } else if (seg.getType() == SegmentType.WALK) { + Direction[] directions = ((MovementSegment) seg).getDirections(); + builder.putBits(1, 1); + builder.putBits(2, 1); + builder.putBits(3, directions[0].toInteger()); + builder.putBits(1, updateRequired ? 1 : 0); + } else { + if (updateRequired) { + builder.putBits(1, 1); + builder.putBits(2, 0); + } else { + builder.putBits(1, 0); + } + } + } + + /** + * Puts the blocks for the specified segment. + * @param segment The segment. + * @param blockBuilder The block builder. + */ + private void putBlocks(SynchronizationSegment segment, GamePacketBuilder blockBuilder) { + SynchronizationBlockSet blockSet = segment.getBlockSet(); + if (blockSet.size() > 0) { + int mask = 0; + + if (blockSet.contains(GraphicBlock.class)) { + mask |= 0x100; + } + + if (blockSet.contains(AnimationBlock.class)){ + mask |= 0x8; + } + + if (blockSet.contains(ChatBlock.class)) { + mask |= 0x80; + } + + if (blockSet.contains(AppearanceBlock.class)) { + mask |= 0x10; + } + + if (blockSet.contains(TurnToPositionBlock.class)) { + mask |= 0x2; + } + + if (mask >= 0x100) { + mask |= 0x40; + blockBuilder.put(DataType.SHORT, DataOrder.LITTLE, mask); + } else { + blockBuilder.put(DataType.BYTE, mask); + } + + if (blockSet.contains(GraphicBlock.class)) { + putGraphicBlock(blockSet.get(GraphicBlock.class), blockBuilder); + } + + if (blockSet.contains(AnimationBlock.class)) { + putAnimationBlock(blockSet.get(AnimationBlock.class), blockBuilder); + } + + if (blockSet.contains(ChatBlock.class)) { + putChatBlock(blockSet.get(ChatBlock.class), blockBuilder); + } + + if (blockSet.contains(AppearanceBlock.class)) { + putAppearanceBlock(blockSet.get(AppearanceBlock.class), blockBuilder); + } + + if (blockSet.contains(TurnToPositionBlock.class)) { + putTurnToPositionBlock(blockSet.get(TurnToPositionBlock.class), blockBuilder); + } + } + } + + /** + * Puts a turn to position block into the specified builder. + * @param block The block. + * @param blockBuilder The builder. + */ + private void putTurnToPositionBlock(TurnToPositionBlock block, GamePacketBuilder blockBuilder) { + Position pos = block.getPosition(); + blockBuilder.put(DataType.SHORT, DataOrder.LITTLE, DataTransformation.ADD, pos.getX() * 2 + 1); + blockBuilder.put(DataType.SHORT, DataOrder.LITTLE, pos.getY() * 2 + 1); + } + + /** + * Puts a graphic block into the specified builder. + * @param block The block. + * @param blockBuilder The builder. + */ + private void putGraphicBlock(GraphicBlock block, GamePacketBuilder blockBuilder) { + Graphic graphic = block.getGraphic(); + blockBuilder.put(DataType.SHORT, DataOrder.LITTLE, graphic.getId()); + blockBuilder.put(DataType.INT, ((graphic.getHeight() >> 16) & 0x0000FFFF) | (graphic.getDelay() & 0xFFFF)); + } + + /** + * Puts an animation block into the specified builder. + * @param block The block. + * @param blockBuilder The builder. + */ + private void putAnimationBlock(AnimationBlock block, GamePacketBuilder blockBuilder) { + Animation animation = block.getAnimation(); + blockBuilder.put(DataType.SHORT, DataOrder.LITTLE, animation.getId()); + blockBuilder.put(DataType.BYTE, DataTransformation.NEGATE, animation.getDelay()); + } + + /** + * Puts a chat block into the specified builder. + * @param block The block. + * @param blockBuilder The builder. + */ + private void putChatBlock(ChatBlock block, GamePacketBuilder blockBuilder) { + byte[] bytes = block.getCompressedMessage(); + blockBuilder.put(DataType.SHORT, DataOrder.LITTLE, (block.getTextColor() << 8) | block.getTextEffects()); + blockBuilder.put(DataType.BYTE, block.getPrivilegeLevel().toInteger()); + blockBuilder.put(DataType.BYTE, DataTransformation.NEGATE, bytes.length); + blockBuilder.putBytesReverse(bytes); + } + + /** + * Puts an appearance block into the specified builder. + * @param block The block. + * @param blockBuilder The builder. + */ + private void putAppearanceBlock(AppearanceBlock block, GamePacketBuilder blockBuilder) { + Appearance appearance = block.getAppearance(); + GamePacketBuilder playerProperties = new GamePacketBuilder(); + + playerProperties.put(DataType.BYTE, appearance.getGender().toInteger()); // gender + playerProperties.put(DataType.BYTE, 0); // skull icon + + Inventory equipment = block.getEquipment(); + int[] style = appearance.getStyle(); + Item item, chest, helm; + + for (int slot = 0; slot < 4; slot++) { + if ((item = equipment.get(slot)) != null) { + playerProperties.put(DataType.SHORT, 0x200 + item.getId()); + } else { + playerProperties.put(DataType.BYTE, 0); + } + } + + if ((chest = equipment.get(EquipmentConstants.CHEST)) != null) { + playerProperties.put(DataType.SHORT, 0x200 + chest.getId()); + } else { + playerProperties.put(DataType.SHORT, 0x100 + style[2]); + } + + if ((item = equipment.get(EquipmentConstants.SHIELD)) != null) { + playerProperties.put(DataType.SHORT, 0x200 + item.getId()); + } else { + playerProperties.put(DataType.BYTE, 0); + } + + if (chest != null) { + EquipmentDefinition def = EquipmentDefinition.forId(chest.getId()); + if (def != null && !def.isFullBody()) { + playerProperties.put(DataType.SHORT, 0x100 + style[3]); + } else { + playerProperties.put(DataType.BYTE, 0); + } + } else { + playerProperties.put(DataType.SHORT, 0x100 + style[3]); + } + + if ((item = equipment.get(EquipmentConstants.LEGS)) != null) { + playerProperties.put(DataType.SHORT, 0x200 + item.getId()); + } else { + playerProperties.put(DataType.SHORT, 0x100 + style[5]); + } + + if ((helm = equipment.get(EquipmentConstants.HAT)) != null) { + EquipmentDefinition def = EquipmentDefinition.forId(helm.getId()); + if (def != null && !def.isFullHat() && !def.isFullMask()) { + playerProperties.put(DataType.SHORT, 0x100 + style[0]); + } else { + playerProperties.put(DataType.BYTE, 0); + } + } else { + playerProperties.put(DataType.SHORT, 0x100 + style[0]); + } + + if ((item = equipment.get(EquipmentConstants.HANDS)) != null) { + playerProperties.put(DataType.SHORT, 0x200 + item.getId()); + } else { + playerProperties.put(DataType.SHORT, 0x100 + style[4]); + } + + if ((item = equipment.get(EquipmentConstants.FEET)) != null) { + playerProperties.put(DataType.SHORT, 0x200 + item.getId()); + } else { + playerProperties.put(DataType.SHORT, 0x100 + style[6]); + } + + EquipmentDefinition def = null; + if (helm != null) { + def = EquipmentDefinition.forId(helm.getId()); + } + if ((def != null && (def.isFullHat() || def.isFullMask())) || appearance.getGender() == Gender.FEMALE) { + playerProperties.put(DataType.BYTE, 0); + } else { + playerProperties.put(DataType.SHORT, 0x100 + style[1]); + } + + int[] colors = appearance.getColors(); + for (int i = 0; i < colors.length; i++) { + playerProperties.put(DataType.BYTE, colors[i]); + } + + playerProperties.put(DataType.SHORT, 0x328); // stand + playerProperties.put(DataType.SHORT, 0x337); // stand turn + playerProperties.put(DataType.SHORT, 0x333); // walk + playerProperties.put(DataType.SHORT, 0x334); // turn 180 + playerProperties.put(DataType.SHORT, 0x335); // turn 90 cw + playerProperties.put(DataType.SHORT, 0x336); // turn 90 ccw + playerProperties.put(DataType.SHORT, 0x338); // run + + playerProperties.put(DataType.LONG, block.getName()); + playerProperties.put(DataType.BYTE, block.getCombatLevel()); // combat level + playerProperties.put(DataType.SHORT, block.getSkillLevel()); // total skill level + + blockBuilder.put(DataType.BYTE, DataTransformation.NEGATE, playerProperties.getLength()); + blockBuilder.putRawBuilder(playerProperties); + } + +} diff --git a/src/org/apollo/net/release/r317/RegionChangeEventEncoder.java b/src/org/apollo/net/release/r317/RegionChangeEventEncoder.java new file mode 100644 index 000000000..a28f9d947 --- /dev/null +++ b/src/org/apollo/net/release/r317/RegionChangeEventEncoder.java @@ -0,0 +1,24 @@ +package org.apollo.net.release.r317; + +import org.apollo.game.event.impl.RegionChangeEvent; +import org.apollo.net.codec.game.DataTransformation; +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.EventEncoder; + +/** + * An {@link EventEncoder} for the {@link RegionChangeEvent}. + * @author Graham + */ +public final class RegionChangeEventEncoder extends EventEncoder { + + @Override + public GamePacket encode(RegionChangeEvent event) { + GamePacketBuilder builder = new GamePacketBuilder(73); + builder.put(DataType.SHORT, DataTransformation.ADD, event.getPosition().getCentralRegionX()); + builder.put(DataType.SHORT, event.getPosition().getCentralRegionY()); + return builder.toGamePacket(); + } + +} diff --git a/src/org/apollo/net/release/r317/Release317.java b/src/org/apollo/net/release/r317/Release317.java new file mode 100644 index 000000000..eea4321bd --- /dev/null +++ b/src/org/apollo/net/release/r317/Release317.java @@ -0,0 +1,108 @@ +package org.apollo.net.release.r317; + +import org.apollo.game.event.impl.CloseInterfaceEvent; +import org.apollo.game.event.impl.EnterAmountEvent; +import org.apollo.game.event.impl.IdAssignmentEvent; +import org.apollo.game.event.impl.LogoutEvent; +import org.apollo.game.event.impl.OpenInterfaceEvent; +import org.apollo.game.event.impl.OpenInterfaceSidebarEvent; +import org.apollo.game.event.impl.PlayerSynchronizationEvent; +import org.apollo.game.event.impl.RegionChangeEvent; +import org.apollo.game.event.impl.ServerMessageEvent; +import org.apollo.game.event.impl.SwitchTabInterfaceEvent; +import org.apollo.game.event.impl.UpdateItemsEvent; +import org.apollo.game.event.impl.UpdateSkillEvent; +import org.apollo.game.event.impl.UpdateSlottedItemsEvent; +import org.apollo.net.meta.PacketMetaDataGroup; +import org.apollo.net.release.Release; + +/** + * An implementation of {@link Release} for the 317 protocol. + * @author Graham + */ +public final class Release317 extends Release { + + /** + * The incoming packet lengths array. + */ + public static final int[] PACKET_LENGTHS = { + 0, 0, 0, 1, -1, 0, 0, 0, 0, 0, // 0 + 0, 0, 0, 0, 8, 0, 6, 2, 2, 0, // 10 + 0, 2, 0, 6, 0, 12, 0, 0, 0, 0, // 20 + 0, 0, 0, 0, 0, 8, 4, 0, 0, 2, // 30 + 2, 6, 0, 6, 0, -1, 0, 0, 0, 0, // 40 + 0, 0, 0, 12, 0, 0, 0, 0, 8, 0, // 50 + 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, // 60 + 6, 0, 2, 2, 8, 6, 0, -1, 0, 6, // 70 + 0, 0, 0, 0, 0, 1, 4, 6, 0, 0, // 80 + 0, 0, 0, 0, 0, 3, 0, 0, -1, 0, // 90 + 0, 13, 0, -1, 0, 0, 0, 0, 0, 0, // 100 + 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, // 110 + 1, 0, 6, 0, 0, 0, -1, 0, 2, 6, // 120 + 0, 4, 6, 8, 0, 6, 0, 0, 0, 2, // 130 + 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, // 140 + 0, 0, 1, 2, 0, 2, 6, 0, 0, 0, // 150 + 0, 0, 0, 0, -1, -1, 0, 0, 0, 0, // 160 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 170 + 0, 8, 0, 3, 0, 2, 0, 0, 8, 1, // 180 + 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, // 190 + 2, 0, 0, 0, 0, 0, 0, 0, 4, 0, // 200 + 4, 0, 0, 0, 7, 8, 0, 0, 10, 0, // 210 + 0, 0, 0, 0, 0, 0, -1, 0, 6, 0, // 220 + 1, 0, 0, 0, 6, 0, 6, 8, 1, 0, // 230 + 0, 4, 0, 0, 0, 0, -1, 0, -1, 4, // 240 + 0, 0, 6, 6, 0, 0, // 250 + }; + + /** + * Creates and initialises this release. + */ + public Release317() { + super(317, PacketMetaDataGroup.createFromArray(PACKET_LENGTHS)); + init(); + } + + /** + * Initialises this release by registering encoders and decoders. + */ + private void init() { + // register decoders + register(0, new KeepAliveEventDecoder()); + register(101, new CharacterDesignEventDecoder()); + WalkEventDecoder walkEventDecoder = new WalkEventDecoder(); + register(248, walkEventDecoder); + register(164, walkEventDecoder); + register(98, walkEventDecoder); + register(4, new ChatEventDecoder()); + register(185, new ButtonEventDecoder()); + register(103, new CommandEventDecoder()); + register(214, new SwitchItemEventDecoder()); + register(132, new FirstObjectActionEventDecoder()); + register(252, new SecondObjectActionEventDecoder()); + register(70, new ThirdObjectActionEventDecoder()); + register(41, new EquipEventDecoder()); + register(145, new FirstItemActionEventDecoder()); + register(117, new SecondItemActionEventDecoder()); + register(43, new ThirdItemActionEventDecoder()); + register(129, new FourthItemActionEventDecoder()); + register(135, new FifthItemActionEventDecoder()); + register(130, new ClosedInterfaceEventDecoder()); + register(208, new EnteredAmountEventDecoder()); + + // register encoders + register(IdAssignmentEvent.class, new IdAssignmentEventEncoder()); + register(RegionChangeEvent.class, new RegionChangeEventEncoder()); + register(ServerMessageEvent.class, new ServerMessageEventEncoder()); + register(PlayerSynchronizationEvent.class, new PlayerSynchronizationEventEncoder()); + register(OpenInterfaceEvent.class, new OpenInterfaceEventEncoder()); + register(CloseInterfaceEvent.class, new CloseInterfaceEventEncoder()); + register(SwitchTabInterfaceEvent.class, new SwitchTabInterfaceEventEncoder()); + register(LogoutEvent.class, new LogoutEventEncoder()); + register(UpdateItemsEvent.class, new UpdateItemsEventEncoder()); + register(UpdateSlottedItemsEvent.class, new UpdateSlottedItemsEventEncoder()); + register(UpdateSkillEvent.class, new UpdateSkillEventEncoder()); + register(OpenInterfaceSidebarEvent.class, new OpenInterfaceSidebarEventEncoder()); + register(EnterAmountEvent.class, new EnterAmountEventEncoder()); + } + +} diff --git a/src/org/apollo/net/release/r317/SecondItemActionEventDecoder.java b/src/org/apollo/net/release/r317/SecondItemActionEventDecoder.java new file mode 100644 index 000000000..1ef2fe6d7 --- /dev/null +++ b/src/org/apollo/net/release/r317/SecondItemActionEventDecoder.java @@ -0,0 +1,26 @@ +package org.apollo.net.release.r317; + +import org.apollo.game.event.impl.SecondItemActionEvent; +import org.apollo.net.codec.game.DataOrder; +import org.apollo.net.codec.game.DataTransformation; +import org.apollo.net.codec.game.DataType; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketReader; +import org.apollo.net.release.EventDecoder; + +/** + * An {@link EventDecoder} for the {@link SecondItemActionEvent}. + * @author Graham + */ +public final class SecondItemActionEventDecoder extends EventDecoder { + + @Override + public SecondItemActionEvent decode(GamePacket packet) { + GamePacketReader reader = new GamePacketReader(packet); + int interfaceId = (int) reader.getUnsigned(DataType.SHORT, DataOrder.LITTLE, DataTransformation.ADD); + int id = (int) reader.getUnsigned(DataType.SHORT, DataOrder.LITTLE, DataTransformation.ADD); + int slot = (int) reader.getUnsigned(DataType.SHORT, DataOrder.LITTLE, DataTransformation.ADD); + return new SecondItemActionEvent(interfaceId, id, slot); + } + +} diff --git a/src/org/apollo/net/release/r317/SecondObjectActionEventDecoder.java b/src/org/apollo/net/release/r317/SecondObjectActionEventDecoder.java new file mode 100644 index 000000000..bf9891fb5 --- /dev/null +++ b/src/org/apollo/net/release/r317/SecondObjectActionEventDecoder.java @@ -0,0 +1,27 @@ +package org.apollo.net.release.r317; + +import org.apollo.game.event.impl.SecondObjectActionEvent; +import org.apollo.game.model.Position; +import org.apollo.net.codec.game.DataOrder; +import org.apollo.net.codec.game.DataTransformation; +import org.apollo.net.codec.game.DataType; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketReader; +import org.apollo.net.release.EventDecoder; + +/** + * An {@link EventDecoder} for the {@link SecondObjectActionEvent}. + * @author Graham + */ +public final class SecondObjectActionEventDecoder extends EventDecoder { + + @Override + public SecondObjectActionEvent decode(GamePacket packet) { + GamePacketReader reader = new GamePacketReader(packet); + int id = (int) reader.getUnsigned(DataType.SHORT, DataOrder.LITTLE, DataTransformation.ADD); + int y = (int) reader.getUnsigned(DataType.SHORT, DataOrder.LITTLE); + int x = (int) reader.getUnsigned(DataType.SHORT, DataTransformation.ADD); + return new SecondObjectActionEvent(id, new Position(x, y)); + } + +} diff --git a/src/org/apollo/net/release/r317/ServerMessageEventEncoder.java b/src/org/apollo/net/release/r317/ServerMessageEventEncoder.java new file mode 100644 index 000000000..7875e0d65 --- /dev/null +++ b/src/org/apollo/net/release/r317/ServerMessageEventEncoder.java @@ -0,0 +1,22 @@ +package org.apollo.net.release.r317; + +import org.apollo.game.event.impl.ServerMessageEvent; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketBuilder; +import org.apollo.net.meta.PacketType; +import org.apollo.net.release.EventEncoder; + +/** + * An {@link EventEncoder} for the {@link ServerMessageEvent}. + * @author Graham + */ +public final class ServerMessageEventEncoder extends EventEncoder { + + @Override + public GamePacket encode(ServerMessageEvent event) { + GamePacketBuilder builder = new GamePacketBuilder(253, PacketType.VARIABLE_BYTE); + builder.putString(event.getMessage()); + return builder.toGamePacket(); + } + +} diff --git a/src/org/apollo/net/release/r317/SwitchItemEventDecoder.java b/src/org/apollo/net/release/r317/SwitchItemEventDecoder.java new file mode 100644 index 000000000..0679181e0 --- /dev/null +++ b/src/org/apollo/net/release/r317/SwitchItemEventDecoder.java @@ -0,0 +1,27 @@ +package org.apollo.net.release.r317; + +import org.apollo.game.event.impl.SwitchItemEvent; +import org.apollo.net.codec.game.DataOrder; +import org.apollo.net.codec.game.DataTransformation; +import org.apollo.net.codec.game.DataType; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketReader; +import org.apollo.net.release.EventDecoder; + +/** + * An {@link EventDecoder} for the {@link SwitchItemEvent}. + * @author Graham + */ +public final class SwitchItemEventDecoder extends EventDecoder { + + @Override + public SwitchItemEvent decode(GamePacket packet) { + GamePacketReader reader = new GamePacketReader(packet); + int interfaceId = (int) reader.getUnsigned(DataType.SHORT, DataOrder.LITTLE, DataTransformation.ADD); + boolean inserting = reader.getUnsigned(DataType.BYTE, DataTransformation.NEGATE) == 1; + int oldSlot = (int) reader.getUnsigned(DataType.SHORT, DataOrder.LITTLE, DataTransformation.ADD); + int newSlot = (int) reader.getUnsigned(DataType.SHORT, DataOrder.LITTLE, DataTransformation.ADD); + return new SwitchItemEvent(interfaceId, inserting, oldSlot, newSlot); + } + +} diff --git a/src/org/apollo/net/release/r317/SwitchTabInterfaceEventEncoder.java b/src/org/apollo/net/release/r317/SwitchTabInterfaceEventEncoder.java new file mode 100644 index 000000000..0a52591bf --- /dev/null +++ b/src/org/apollo/net/release/r317/SwitchTabInterfaceEventEncoder.java @@ -0,0 +1,24 @@ +package org.apollo.net.release.r317; + +import org.apollo.game.event.impl.SwitchTabInterfaceEvent; +import org.apollo.net.codec.game.DataTransformation; +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.EventEncoder; + +/** + * An {@link EventEncoder} for the {@link SwitchTabInterfaceEvent}. + * @author Graham + */ +public final class SwitchTabInterfaceEventEncoder extends EventEncoder { + + @Override + public GamePacket encode(SwitchTabInterfaceEvent event) { + GamePacketBuilder builder = new GamePacketBuilder(71); + builder.put(DataType.SHORT, event.getInterfaceId()); + builder.put(DataType.BYTE, DataTransformation.ADD, event.getTabId()); + return builder.toGamePacket(); + } + +} diff --git a/src/org/apollo/net/release/r317/ThirdItemActionEventDecoder.java b/src/org/apollo/net/release/r317/ThirdItemActionEventDecoder.java new file mode 100644 index 000000000..8b9e783a8 --- /dev/null +++ b/src/org/apollo/net/release/r317/ThirdItemActionEventDecoder.java @@ -0,0 +1,26 @@ +package org.apollo.net.release.r317; + +import org.apollo.game.event.impl.ThirdItemActionEvent; +import org.apollo.net.codec.game.DataOrder; +import org.apollo.net.codec.game.DataTransformation; +import org.apollo.net.codec.game.DataType; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketReader; +import org.apollo.net.release.EventDecoder; + +/** + * An {@link EventDecoder} for the {@link ThirdItemActionEvent}. + * @author Graham + */ +public final class ThirdItemActionEventDecoder extends EventDecoder { + + @Override + public ThirdItemActionEvent decode(GamePacket packet) { + GamePacketReader reader = new GamePacketReader(packet); + int interfaceId = (int) reader.getUnsigned(DataType.SHORT, DataOrder.LITTLE, DataTransformation.ADD); + int id = (int) reader.getUnsigned(DataType.SHORT, DataTransformation.ADD); + int slot = (int) reader.getUnsigned(DataType.SHORT, DataTransformation.ADD); + return new ThirdItemActionEvent(interfaceId, id, slot); + } + +} diff --git a/src/org/apollo/net/release/r317/ThirdObjectActionEventDecoder.java b/src/org/apollo/net/release/r317/ThirdObjectActionEventDecoder.java new file mode 100644 index 000000000..b42b76d8e --- /dev/null +++ b/src/org/apollo/net/release/r317/ThirdObjectActionEventDecoder.java @@ -0,0 +1,28 @@ +package org.apollo.net.release.r317; + +import org.apollo.game.event.impl.ThirdObjectActionEvent; +import org.apollo.game.model.Position; +import org.apollo.net.codec.game.DataOrder; +import org.apollo.net.codec.game.DataTransformation; +import org.apollo.net.codec.game.DataType; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketReader; +import org.apollo.net.release.EventDecoder; + +/** + * An {@link EventDecoder} for the {@link ThirdObjectActionEvent}. + * @author Graham + */ +public final class ThirdObjectActionEventDecoder extends EventDecoder { + + @Override + public ThirdObjectActionEvent decode(GamePacket packet) { + // TODO ripped out of some Winterlove-based server, so probably wrong + GamePacketReader reader = new GamePacketReader(packet); + int x = (int) reader.getUnsigned(DataType.SHORT, DataOrder.LITTLE); + int y = (int) reader.getUnsigned(DataType.SHORT); + int id = (int) reader.getUnsigned(DataType.SHORT, DataOrder.LITTLE, DataTransformation.ADD); + return new ThirdObjectActionEvent(id, new Position(x, y)); + } + +} diff --git a/src/org/apollo/net/release/r317/UpdateItemsEventEncoder.java b/src/org/apollo/net/release/r317/UpdateItemsEventEncoder.java new file mode 100644 index 000000000..3e27a3e9f --- /dev/null +++ b/src/org/apollo/net/release/r317/UpdateItemsEventEncoder.java @@ -0,0 +1,46 @@ +package org.apollo.net.release.r317; + +import org.apollo.game.event.impl.UpdateItemsEvent; +import org.apollo.game.model.Item; +import org.apollo.net.codec.game.DataOrder; +import org.apollo.net.codec.game.DataTransformation; +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.meta.PacketType; +import org.apollo.net.release.EventEncoder; + +/** + * An {@link EventEncoder} for the {@link UpdateItemsEvent}. + * @author Graham + */ +public final class UpdateItemsEventEncoder extends EventEncoder { + + @Override + public GamePacket encode(UpdateItemsEvent event) { + GamePacketBuilder builder = new GamePacketBuilder(53, PacketType.VARIABLE_SHORT); + + Item[] items = event.getItems(); + + builder.put(DataType.SHORT, event.getInterfaceId()); + builder.put(DataType.SHORT, items.length); + + for (int i = 0; i < items.length; i++) { + Item item = items[i]; + int id = item == null ? -1 : item.getId(); + int amount = item == null ? 0 : item.getAmount(); + + if (amount > 254) { + builder.put(DataType.BYTE, 255); + builder.put(DataType.INT, DataOrder.INVERSED_MIDDLE, amount); + } else { + builder.put(DataType.BYTE, amount); + } + + builder.put(DataType.SHORT, DataOrder.LITTLE, DataTransformation.ADD, id + 1); + } + + return builder.toGamePacket(); + } + +} diff --git a/src/org/apollo/net/release/r317/UpdateSkillEventEncoder.java b/src/org/apollo/net/release/r317/UpdateSkillEventEncoder.java new file mode 100644 index 000000000..bf2893efb --- /dev/null +++ b/src/org/apollo/net/release/r317/UpdateSkillEventEncoder.java @@ -0,0 +1,29 @@ +package org.apollo.net.release.r317; + +import org.apollo.game.event.impl.UpdateSkillEvent; +import org.apollo.game.model.Skill; +import org.apollo.net.codec.game.DataOrder; +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.EventEncoder; + +/** + * An {@link EventEncoder} for the {@link UpdateSkillEvent}. + * @author Graham + */ +public final class UpdateSkillEventEncoder extends EventEncoder { + + @Override + public GamePacket encode(UpdateSkillEvent event) { + GamePacketBuilder builder = new GamePacketBuilder(134); + Skill skill = event.getSkill(); + + builder.put(DataType.BYTE, event.getId()); + builder.put(DataType.INT, DataOrder.MIDDLE, (int) skill.getExperience()); + builder.put(DataType.BYTE, skill.getCurrentLevel()); + + return builder.toGamePacket(); + } + +} diff --git a/src/org/apollo/net/release/r317/UpdateSlottedItemsEventEncoder.java b/src/org/apollo/net/release/r317/UpdateSlottedItemsEventEncoder.java new file mode 100644 index 000000000..919bd3e7e --- /dev/null +++ b/src/org/apollo/net/release/r317/UpdateSlottedItemsEventEncoder.java @@ -0,0 +1,45 @@ +package org.apollo.net.release.r317; + +import org.apollo.game.event.impl.UpdateSlottedItemsEvent; +import org.apollo.game.model.Item; +import org.apollo.game.model.SlottedItem; +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.meta.PacketType; +import org.apollo.net.release.EventEncoder; + +/** + * An {@link EventEncoder} for the {@link UpdateSlottedItemsEvent}. + * @author Graham + */ +public final class UpdateSlottedItemsEventEncoder extends EventEncoder { + + @Override + public GamePacket encode(UpdateSlottedItemsEvent event) { + GamePacketBuilder builder = new GamePacketBuilder(34, PacketType.VARIABLE_SHORT); + SlottedItem[] items = event.getSlottedItems(); + + builder.put(DataType.SHORT, event.getInterfaceId()); + + for (SlottedItem slottedItem : items) { + builder.putSmart(slottedItem.getSlot()); + + Item item = slottedItem.getItem(); + int id = item == null ? -1 : item.getId(); + int amount = item == null ? 0 : item.getAmount(); + + builder.put(DataType.SHORT, id + 1); + + if (amount > 254) { + builder.put(DataType.BYTE, 255); + builder.put(DataType.INT, amount); + } else { + builder.put(DataType.BYTE, amount); + } + } + + return builder.toGamePacket(); + } + +} diff --git a/src/org/apollo/net/release/r317/WalkEventDecoder.java b/src/org/apollo/net/release/r317/WalkEventDecoder.java new file mode 100644 index 000000000..1bf20d7ee --- /dev/null +++ b/src/org/apollo/net/release/r317/WalkEventDecoder.java @@ -0,0 +1,47 @@ +package org.apollo.net.release.r317; + +import org.apollo.game.event.impl.WalkEvent; +import org.apollo.game.model.Position; +import org.apollo.net.codec.game.DataOrder; +import org.apollo.net.codec.game.DataTransformation; +import org.apollo.net.codec.game.DataType; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketReader; +import org.apollo.net.release.EventDecoder; + +/** + * An {@link EventDecoder} for the {@link WalkEvent}. + * @author Graham + */ +public final class WalkEventDecoder extends EventDecoder { + + @Override + public WalkEvent decode(GamePacket packet) { + GamePacketReader reader = new GamePacketReader(packet); + + int length = packet.getLength(); + if (packet.getOpcode() == 248) { + length -= 14; // strip off anti-cheat data + } + + int steps = (length - 5) / 2; + int[][] path = new int[steps][2]; + + int x = (int) reader.getUnsigned(DataType.SHORT, DataOrder.LITTLE, DataTransformation.ADD); + for (int i = 0; i < steps; i++) { + path[i][0] = (int) reader.getSigned(DataType.BYTE); + path[i][1] = (int) reader.getSigned(DataType.BYTE); + } + int y = (int) reader.getUnsigned(DataType.SHORT, DataOrder.LITTLE); + boolean run = reader.getUnsigned(DataType.BYTE, DataTransformation.NEGATE) == 1; + + Position[] positions = new Position[steps + 1]; + positions[0] = new Position(x, y); + for (int i = 0; i < steps; i++) { + positions[i + 1] = new Position(path[i][0] + x, path[i][1] + y); + } + + return new WalkEvent(positions, run); + } + +} diff --git a/src/org/apollo/net/release/r317/package-info.java b/src/org/apollo/net/release/r317/package-info.java new file mode 100644 index 000000000..eac8d8a67 --- /dev/null +++ b/src/org/apollo/net/release/r317/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains codecs for the 317 release. + */ +package org.apollo.net.release.r317; diff --git a/src/org/apollo/net/release/r377/ButtonEventDecoder.java b/src/org/apollo/net/release/r377/ButtonEventDecoder.java new file mode 100644 index 000000000..aa967ae68 --- /dev/null +++ b/src/org/apollo/net/release/r377/ButtonEventDecoder.java @@ -0,0 +1,22 @@ +package org.apollo.net.release.r377; + +import org.apollo.game.event.impl.ButtonEvent; +import org.apollo.net.codec.game.DataType; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketReader; +import org.apollo.net.release.EventDecoder; + +/** + * An {@link EventDecoder} for the {@link ButtonEvent}. + * @author Graham + */ +public final class ButtonEventDecoder extends EventDecoder { + + @Override + public ButtonEvent decode(GamePacket packet) { + GamePacketReader reader = new GamePacketReader(packet); + int interfaceId = (int) reader.getUnsigned(DataType.SHORT); + return new ButtonEvent(interfaceId); + } + +} diff --git a/src/org/apollo/net/release/r377/CharacterDesignEventDecoder.java b/src/org/apollo/net/release/r377/CharacterDesignEventDecoder.java new file mode 100644 index 000000000..80585eddf --- /dev/null +++ b/src/org/apollo/net/release/r377/CharacterDesignEventDecoder.java @@ -0,0 +1,38 @@ +package org.apollo.net.release.r377; + +import org.apollo.game.event.impl.CharacterDesignEvent; +import org.apollo.game.model.Appearance; +import org.apollo.game.model.Gender; +import org.apollo.net.codec.game.DataType; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketReader; +import org.apollo.net.release.EventDecoder; + +/** + * An {@link EventDecoder} for the {@link CharacterDesignEvent}. + * @author Graham + */ +public final class CharacterDesignEventDecoder extends EventDecoder { + + @Override + public CharacterDesignEvent decode(GamePacket packet) { + GamePacketReader reader = new GamePacketReader(packet); + + int genderIntValue = (int) reader.getUnsigned(DataType.BYTE); + + int[] style = new int[7]; + for (int i = 0; i < style.length; i++) { + style[i] = (int) reader.getUnsigned(DataType.BYTE); + } + + int[] color = new int[5]; + for (int i = 0; i < color.length; i++) { + color[i] = (int) reader.getUnsigned(DataType.BYTE); + } + + Gender gender = genderIntValue == Gender.MALE.toInteger() ? Gender.MALE : Gender.FEMALE; + + return new CharacterDesignEvent(new Appearance(gender, style, color)); + } + +} diff --git a/src/org/apollo/net/release/r377/ChatEventDecoder.java b/src/org/apollo/net/release/r377/ChatEventDecoder.java new file mode 100644 index 000000000..252c433b1 --- /dev/null +++ b/src/org/apollo/net/release/r377/ChatEventDecoder.java @@ -0,0 +1,39 @@ +package org.apollo.net.release.r377; + +import org.apollo.game.event.impl.ChatEvent; +import org.apollo.net.codec.game.DataTransformation; +import org.apollo.net.codec.game.DataType; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketReader; +import org.apollo.net.release.EventDecoder; +import org.apollo.util.TextUtil; + +/** + * An {@link EventDecoder} for the {@link ChatEvent}. + * @author Graham + */ +public final class ChatEventDecoder extends EventDecoder { + + @Override + public ChatEvent decode(GamePacket packet) { + GamePacketReader reader = new GamePacketReader(packet); + + int color = (int) reader.getUnsigned(DataType.BYTE, DataTransformation.NEGATE); + int effects = (int) reader.getUnsigned(DataType.BYTE, DataTransformation.ADD); + + int length = packet.getLength() - 2; + + byte[] originalCompressed = new byte[length]; + reader.getBytes(originalCompressed); + + String uncompressed = TextUtil.uncompress(originalCompressed, length); + uncompressed = TextUtil.filterInvalidCharacters(uncompressed); + uncompressed = TextUtil.capitalize(uncompressed); + + byte[] recompressed = new byte[length]; + TextUtil.compress(uncompressed, recompressed); // in case invalid data gets sent, this effectively verifies it + + return new ChatEvent(uncompressed, recompressed, color, effects); + } + +} diff --git a/src/org/apollo/net/release/r377/CloseInterfaceEventEncoder.java b/src/org/apollo/net/release/r377/CloseInterfaceEventEncoder.java new file mode 100644 index 000000000..1bfd85291 --- /dev/null +++ b/src/org/apollo/net/release/r377/CloseInterfaceEventEncoder.java @@ -0,0 +1,20 @@ +package org.apollo.net.release.r377; + +import org.apollo.game.event.impl.CloseInterfaceEvent; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketBuilder; +import org.apollo.net.release.EventEncoder; + +/** + * An {@link EventEncoder} for the {@link CloseInterfaceEvent}. + * @author Graham + */ +public final class CloseInterfaceEventEncoder extends EventEncoder { + + @Override + public GamePacket encode(CloseInterfaceEvent event) { + GamePacketBuilder builder = new GamePacketBuilder(29); + return builder.toGamePacket(); + } + +} diff --git a/src/org/apollo/net/release/r377/ClosedInterfaceEventDecoder.java b/src/org/apollo/net/release/r377/ClosedInterfaceEventDecoder.java new file mode 100644 index 000000000..20df1b637 --- /dev/null +++ b/src/org/apollo/net/release/r377/ClosedInterfaceEventDecoder.java @@ -0,0 +1,18 @@ +package org.apollo.net.release.r377; + +import org.apollo.game.event.impl.ClosedInterfaceEvent; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.release.EventDecoder; + +/** + * An {@link EventDecoder} for the {@link ClosedInterfaceEvent}. + * @author Graham + */ +public final class ClosedInterfaceEventDecoder extends EventDecoder { + + @Override + public ClosedInterfaceEvent decode(GamePacket packet) { + return new ClosedInterfaceEvent(); + } + +} diff --git a/src/org/apollo/net/release/r377/CommandEventDecoder.java b/src/org/apollo/net/release/r377/CommandEventDecoder.java new file mode 100644 index 000000000..151e397a4 --- /dev/null +++ b/src/org/apollo/net/release/r377/CommandEventDecoder.java @@ -0,0 +1,20 @@ +package org.apollo.net.release.r377; + +import org.apollo.game.event.impl.CommandEvent; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketReader; +import org.apollo.net.release.EventDecoder; + +/** + * An {@link EventDecoder} for the {@link CommandEvent}. + * @author Graham + */ +public final class CommandEventDecoder extends EventDecoder { + + @Override + public CommandEvent decode(GamePacket packet) { + GamePacketReader reader = new GamePacketReader(packet); + return new CommandEvent(reader.getString()); + } + +} diff --git a/src/org/apollo/net/release/r377/EnterAmountEventEncoder.java b/src/org/apollo/net/release/r377/EnterAmountEventEncoder.java new file mode 100644 index 000000000..c74cfa8e0 --- /dev/null +++ b/src/org/apollo/net/release/r377/EnterAmountEventEncoder.java @@ -0,0 +1,20 @@ +package org.apollo.net.release.r377; + +import org.apollo.game.event.impl.EnterAmountEvent; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.meta.PacketType; +import org.apollo.net.release.EventEncoder; +import org.jboss.netty.buffer.ChannelBuffers; + +/** + * An {@link EventEncoder} for the {@link EnterAmountEvent}. + * @author Graham + */ +public final class EnterAmountEventEncoder extends EventEncoder { + + @Override + public GamePacket encode(EnterAmountEvent event) { + return new GamePacket(58, PacketType.FIXED, ChannelBuffers.EMPTY_BUFFER); + } + +} diff --git a/src/org/apollo/net/release/r377/EnteredAmountEventDecoder.java b/src/org/apollo/net/release/r377/EnteredAmountEventDecoder.java new file mode 100644 index 000000000..2a18a1750 --- /dev/null +++ b/src/org/apollo/net/release/r377/EnteredAmountEventDecoder.java @@ -0,0 +1,22 @@ +package org.apollo.net.release.r377; + +import org.apollo.game.event.impl.EnteredAmountEvent; +import org.apollo.net.codec.game.DataType; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketReader; +import org.apollo.net.release.EventDecoder; + +/** + * An {@link EventDecoder} for the {@link EnteredAmountEvent}. + * @author Graham + */ +public final class EnteredAmountEventDecoder extends EventDecoder { + + @Override + public EnteredAmountEvent decode(GamePacket packet) { + GamePacketReader reader = new GamePacketReader(packet); + int amount = (int) reader.getUnsigned(DataType.INT); + return new EnteredAmountEvent(amount); + } + +} diff --git a/src/org/apollo/net/release/r377/EquipEventDecoder.java b/src/org/apollo/net/release/r377/EquipEventDecoder.java new file mode 100644 index 000000000..3f0ecf573 --- /dev/null +++ b/src/org/apollo/net/release/r377/EquipEventDecoder.java @@ -0,0 +1,26 @@ +package org.apollo.net.release.r377; + +import org.apollo.game.event.impl.EquipEvent; +import org.apollo.net.codec.game.DataOrder; +import org.apollo.net.codec.game.DataTransformation; +import org.apollo.net.codec.game.DataType; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketReader; +import org.apollo.net.release.EventDecoder; + +/** + * An {@link EventDecoder} for the {@link EquipEvent}. + * @author Graham + */ +public final class EquipEventDecoder extends EventDecoder { + + @Override + public EquipEvent decode(GamePacket packet) { + GamePacketReader reader = new GamePacketReader(packet); + int interfaceId = (int) reader.getUnsigned(DataType.SHORT, DataOrder.LITTLE); + int id = (int) reader.getUnsigned(DataType.SHORT, DataOrder.LITTLE); + int slot = (int) reader.getUnsigned(DataType.SHORT, DataTransformation.ADD); + return new EquipEvent(interfaceId, id, slot); + } + +} diff --git a/src/org/apollo/net/release/r377/FifthItemActionEventDecoder.java b/src/org/apollo/net/release/r377/FifthItemActionEventDecoder.java new file mode 100644 index 000000000..ce9bc50d6 --- /dev/null +++ b/src/org/apollo/net/release/r377/FifthItemActionEventDecoder.java @@ -0,0 +1,26 @@ +package org.apollo.net.release.r377; + +import org.apollo.game.event.impl.FifthItemActionEvent; +import org.apollo.net.codec.game.DataOrder; +import org.apollo.net.codec.game.DataTransformation; +import org.apollo.net.codec.game.DataType; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketReader; +import org.apollo.net.release.EventDecoder; + +/** + * An {@link EventDecoder} for the {@link FifthItemActionEvent}. + * @author Graham + */ +public final class FifthItemActionEventDecoder extends EventDecoder { + + @Override + public FifthItemActionEvent decode(GamePacket packet) { + GamePacketReader reader = new GamePacketReader(packet); + int slot = (int) reader.getUnsigned(DataType.SHORT, DataOrder.LITTLE, DataTransformation.ADD); + int id = (int) reader.getUnsigned(DataType.SHORT, DataOrder.LITTLE, DataTransformation.ADD); + int interfaceId = (int) reader.getUnsigned(DataType.SHORT, DataOrder.LITTLE); + return new FifthItemActionEvent(interfaceId, id, slot); + } + +} diff --git a/src/org/apollo/net/release/r377/FirstItemActionEventDecoder.java b/src/org/apollo/net/release/r377/FirstItemActionEventDecoder.java new file mode 100644 index 000000000..2ec7997b1 --- /dev/null +++ b/src/org/apollo/net/release/r377/FirstItemActionEventDecoder.java @@ -0,0 +1,25 @@ +package org.apollo.net.release.r377; + +import org.apollo.game.event.impl.FirstItemActionEvent; +import org.apollo.net.codec.game.DataTransformation; +import org.apollo.net.codec.game.DataType; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketReader; +import org.apollo.net.release.EventDecoder; + +/** + * An {@link EventDecoder} for the {@link FirstItemActionEvent}. + * @author Graham + */ +public final class FirstItemActionEventDecoder extends EventDecoder { + + @Override + public FirstItemActionEvent decode(GamePacket packet) { + GamePacketReader reader = new GamePacketReader(packet); + int id = (int) reader.getUnsigned(DataType.SHORT, DataTransformation.ADD); + int interfaceId = (int) reader.getUnsigned(DataType.SHORT); + int slot = (int) reader.getUnsigned(DataType.SHORT); + return new FirstItemActionEvent(interfaceId, id, slot); + } + +} diff --git a/src/org/apollo/net/release/r377/FirstObjectActionEventDecoder.java b/src/org/apollo/net/release/r377/FirstObjectActionEventDecoder.java new file mode 100644 index 000000000..b7e8f0862 --- /dev/null +++ b/src/org/apollo/net/release/r377/FirstObjectActionEventDecoder.java @@ -0,0 +1,27 @@ +package org.apollo.net.release.r377; + +import org.apollo.game.event.impl.FirstObjectActionEvent; +import org.apollo.game.model.Position; +import org.apollo.net.codec.game.DataOrder; +import org.apollo.net.codec.game.DataTransformation; +import org.apollo.net.codec.game.DataType; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketReader; +import org.apollo.net.release.EventDecoder; + +/** + * An {@link EventDecoder} for the {@link FirstObjectActionEvent}. + * @author Graham + */ +public final class FirstObjectActionEventDecoder extends EventDecoder { + + @Override + public FirstObjectActionEvent decode(GamePacket packet) { + GamePacketReader reader = new GamePacketReader(packet); + int x = (int) reader.getUnsigned(DataType.SHORT, DataTransformation.ADD); + int y = (int) reader.getUnsigned(DataType.SHORT, DataOrder.LITTLE); + int id = (int) reader.getUnsigned(DataType.SHORT, DataOrder.LITTLE); + return new FirstObjectActionEvent(id, new Position(x, y)); + } + +} diff --git a/src/org/apollo/net/release/r377/FourthItemActionEventDecoder.java b/src/org/apollo/net/release/r377/FourthItemActionEventDecoder.java new file mode 100644 index 000000000..9edb1f637 --- /dev/null +++ b/src/org/apollo/net/release/r377/FourthItemActionEventDecoder.java @@ -0,0 +1,26 @@ +package org.apollo.net.release.r377; + +import org.apollo.game.event.impl.FourthItemActionEvent; +import org.apollo.net.codec.game.DataOrder; +import org.apollo.net.codec.game.DataTransformation; +import org.apollo.net.codec.game.DataType; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketReader; +import org.apollo.net.release.EventDecoder; + +/** + * An {@link EventDecoder} for the {@link FourthItemActionEvent}. + * @author Graham + */ +public final class FourthItemActionEventDecoder extends EventDecoder { + + @Override + public FourthItemActionEvent decode(GamePacket packet) { + GamePacketReader reader = new GamePacketReader(packet); + int interfaceId = (int) reader.getUnsigned(DataType.SHORT, DataOrder.LITTLE, DataTransformation.ADD); + int slot = (int) reader.getUnsigned(DataType.SHORT, DataOrder.LITTLE); + int id = (int) reader.getUnsigned(DataType.SHORT); + return new FourthItemActionEvent(interfaceId, id, slot); + } + +} diff --git a/src/org/apollo/net/release/r377/IdAssignmentEventEncoder.java b/src/org/apollo/net/release/r377/IdAssignmentEventEncoder.java new file mode 100644 index 000000000..8e95fb147 --- /dev/null +++ b/src/org/apollo/net/release/r377/IdAssignmentEventEncoder.java @@ -0,0 +1,24 @@ +package org.apollo.net.release.r377; + +import org.apollo.game.event.impl.IdAssignmentEvent; +import org.apollo.net.codec.game.DataOrder; +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.EventEncoder; + +/** + * An {@link EventEncoder} for the {@link IdAssignmentEvent}. + * @author Graham + */ +public final class IdAssignmentEventEncoder extends EventEncoder { + + @Override + public GamePacket encode(IdAssignmentEvent event) { + GamePacketBuilder builder = new GamePacketBuilder(126); + builder.put(DataType.BYTE, event.isMembers() ? 1 : 0); + builder.put(DataType.SHORT, DataOrder.LITTLE, event.getId()); + return builder.toGamePacket(); + } + +} diff --git a/src/org/apollo/net/release/r377/KeepAliveEventDecoder.java b/src/org/apollo/net/release/r377/KeepAliveEventDecoder.java new file mode 100644 index 000000000..51191c4ed --- /dev/null +++ b/src/org/apollo/net/release/r377/KeepAliveEventDecoder.java @@ -0,0 +1,18 @@ +package org.apollo.net.release.r377; + +import org.apollo.game.event.impl.KeepAliveEvent; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.release.EventDecoder; + +/** + * A {@link EventDecoder} for the {@link KeepAliveEvent}. + * @author Graham + */ +public final class KeepAliveEventDecoder extends EventDecoder { + + @Override + public KeepAliveEvent decode(GamePacket packet) { + return new KeepAliveEvent(); + } + +} diff --git a/src/org/apollo/net/release/r377/LogoutEventEncoder.java b/src/org/apollo/net/release/r377/LogoutEventEncoder.java new file mode 100644 index 000000000..7c13a46c0 --- /dev/null +++ b/src/org/apollo/net/release/r377/LogoutEventEncoder.java @@ -0,0 +1,20 @@ +package org.apollo.net.release.r377; + +import org.apollo.game.event.impl.LogoutEvent; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketBuilder; +import org.apollo.net.release.EventEncoder; + +/** + * An {@link EventEncoder} for the {@link LogoutEvent}. + * @author Graham + */ +public final class LogoutEventEncoder extends EventEncoder { + + @Override + public GamePacket encode(LogoutEvent event) { + GamePacketBuilder builder = new GamePacketBuilder(5); + return builder.toGamePacket(); + } + +} diff --git a/src/org/apollo/net/release/r377/OpenInterfaceEventEncoder.java b/src/org/apollo/net/release/r377/OpenInterfaceEventEncoder.java new file mode 100644 index 000000000..d91340f71 --- /dev/null +++ b/src/org/apollo/net/release/r377/OpenInterfaceEventEncoder.java @@ -0,0 +1,24 @@ +package org.apollo.net.release.r377; + +import org.apollo.game.event.impl.OpenInterfaceEvent; +import org.apollo.net.codec.game.DataOrder; +import org.apollo.net.codec.game.DataTransformation; +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.EventEncoder; + +/** + * An {@link EventEncoder} for the {@link OpenInterfaceEvent}. + * @author Graham + */ +public final class OpenInterfaceEventEncoder extends EventEncoder { + + @Override + public GamePacket encode(OpenInterfaceEvent event) { + GamePacketBuilder builder = new GamePacketBuilder(159); + builder.put(DataType.SHORT, DataOrder.LITTLE, DataTransformation.ADD, event.getId()); + return builder.toGamePacket(); + } + +} diff --git a/src/org/apollo/net/release/r377/OpenInterfaceSidebarEventEncoder.java b/src/org/apollo/net/release/r377/OpenInterfaceSidebarEventEncoder.java new file mode 100644 index 000000000..010bf185f --- /dev/null +++ b/src/org/apollo/net/release/r377/OpenInterfaceSidebarEventEncoder.java @@ -0,0 +1,25 @@ +package org.apollo.net.release.r377; + +import org.apollo.game.event.impl.OpenInterfaceSidebarEvent; +import org.apollo.net.codec.game.DataOrder; +import org.apollo.net.codec.game.DataTransformation; +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.EventEncoder; + +/** + * An {@link EventEncoder} for the {@link OpenInterfaceSidebarEvent}. + * @author Graham + */ +public final class OpenInterfaceSidebarEventEncoder extends EventEncoder { + + @Override + public GamePacket encode(OpenInterfaceSidebarEvent event) { + GamePacketBuilder builder = new GamePacketBuilder(128); + builder.put(DataType.SHORT, DataTransformation.ADD, event.getInterfaceId()); + builder.put(DataType.SHORT, DataOrder.LITTLE, DataTransformation.ADD, event.getSidebarId()); + return builder.toGamePacket(); + } + +} diff --git a/src/org/apollo/net/release/r377/PlayerSynchronizationEventEncoder.java b/src/org/apollo/net/release/r377/PlayerSynchronizationEventEncoder.java new file mode 100644 index 000000000..ac0f581ed --- /dev/null +++ b/src/org/apollo/net/release/r377/PlayerSynchronizationEventEncoder.java @@ -0,0 +1,354 @@ +package org.apollo.net.release.r377; + +import org.apollo.game.event.impl.PlayerSynchronizationEvent; +import org.apollo.game.model.Animation; +import org.apollo.game.model.Appearance; +import org.apollo.game.model.Direction; +import org.apollo.game.model.EquipmentConstants; +import org.apollo.game.model.Gender; +import org.apollo.game.model.Graphic; +import org.apollo.game.model.Inventory; +import org.apollo.game.model.Item; +import org.apollo.game.model.Position; +import org.apollo.game.model.def.EquipmentDefinition; +import org.apollo.game.sync.block.AnimationBlock; +import org.apollo.game.sync.block.AppearanceBlock; +import org.apollo.game.sync.block.ChatBlock; +import org.apollo.game.sync.block.GraphicBlock; +import org.apollo.game.sync.block.SynchronizationBlockSet; +import org.apollo.game.sync.block.TurnToPositionBlock; +import org.apollo.game.sync.seg.AddCharacterSegment; +import org.apollo.game.sync.seg.MovementSegment; +import org.apollo.game.sync.seg.SegmentType; +import org.apollo.game.sync.seg.SynchronizationSegment; +import org.apollo.game.sync.seg.TeleportSegment; +import org.apollo.net.codec.game.DataOrder; +import org.apollo.net.codec.game.DataTransformation; +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.meta.PacketType; +import org.apollo.net.release.EventEncoder; + +/** + * An {@link EventEncoder} for the {@link PlayerSynchronizationEvent}. + * @author Graham + */ +public final class PlayerSynchronizationEventEncoder extends EventEncoder { + + @Override + public GamePacket encode(PlayerSynchronizationEvent event) { + GamePacketBuilder builder = new GamePacketBuilder(90, PacketType.VARIABLE_SHORT); + builder.switchToBitAccess(); + + GamePacketBuilder blockBuilder = new GamePacketBuilder(); + + putMovementUpdate(event.getSegment(), event, builder); + putBlocks(event.getSegment(), blockBuilder); + + builder.putBits(8, event.getLocalPlayers()); + + for (SynchronizationSegment segment : event.getSegments()) { + SegmentType type = segment.getType(); + if (type == SegmentType.REMOVE_CHARACTER) { + putRemoveCharacterUpdate(builder); + } else if (type == SegmentType.ADD_CHARACTER) { + putAddCharacterUpdate((AddCharacterSegment) segment, event, builder); + putBlocks(segment, blockBuilder); + } else { + putMovementUpdate(segment, event, builder); + putBlocks(segment, blockBuilder); + } + } + + if (blockBuilder.getLength() > 0) { + builder.putBits(11, 2047); + builder.switchToByteAccess(); + builder.putRawBuilder(blockBuilder); + } else { + builder.switchToByteAccess(); + } + + return builder.toGamePacket(); + } + + /** + * Puts a remove character update. + * @param builder The builder. + */ + private void putRemoveCharacterUpdate(GamePacketBuilder builder) { + builder.putBits(1, 1); + builder.putBits(2, 3); + } + + /** + * Puts an add character update. + * @param seg The segment. + * @param event The event. + * @param builder The builder. + */ + private void putAddCharacterUpdate(AddCharacterSegment seg, PlayerSynchronizationEvent event, GamePacketBuilder builder) { + boolean updateRequired = seg.getBlockSet().size() > 0; + Position player = event.getPosition(); + Position other = seg.getPosition(); + builder.putBits(11, seg.getIndex()); + builder.putBits(5, other.getX() - player.getX()); + builder.putBits(1, updateRequired ? 1 : 0); + builder.putBits(1, 1); // discard walking queue? + builder.putBits(5, other.getY() - player.getY()); + } + + /** + * Puts a movement update for the specified segment. + * @param seg The segment. + * @param event The event. + * @param builder The builder. + */ + private void putMovementUpdate(SynchronizationSegment seg, PlayerSynchronizationEvent event, GamePacketBuilder builder) { + boolean updateRequired = seg.getBlockSet().size() > 0; + if (seg.getType() == SegmentType.TELEPORT) { + Position pos = ((TeleportSegment) seg).getDestination(); + builder.putBits(1, 1); + builder.putBits(2, 3); + builder.putBits(1, event.hasRegionChanged() ? 0 : 1); + builder.putBits(2, pos.getHeight()); + builder.putBits(7, pos.getLocalY(event.getLastKnownRegion())); + builder.putBits(7, pos.getLocalX(event.getLastKnownRegion())); + builder.putBits(1, updateRequired ? 1 : 0); + } else if (seg.getType() == SegmentType.RUN) { + Direction[] directions = ((MovementSegment) seg).getDirections(); + builder.putBits(1, 1); + builder.putBits(2, 2); + builder.putBits(3, directions[0].toInteger()); + builder.putBits(3, directions[1].toInteger()); + builder.putBits(1, updateRequired ? 1 : 0); + } else if (seg.getType() == SegmentType.WALK) { + Direction[] directions = ((MovementSegment) seg).getDirections(); + builder.putBits(1, 1); + builder.putBits(2, 1); + builder.putBits(3, directions[0].toInteger()); + builder.putBits(1, updateRequired ? 1 : 0); + } else { + if (updateRequired) { + builder.putBits(1, 1); + builder.putBits(2, 0); + } else { + builder.putBits(1, 0); + } + } + } + + /** + * Puts the blocks for the specified segment. + * @param segment The segment. + * @param blockBuilder The block builder. + */ + private void putBlocks(SynchronizationSegment segment, GamePacketBuilder blockBuilder) { + SynchronizationBlockSet blockSet = segment.getBlockSet(); + if (blockSet.size() > 0) { + int mask = 0; + + if (blockSet.contains(AnimationBlock.class)) { + mask |= 0x8; + } + + if (blockSet.contains(ChatBlock.class)) { + mask |= 0x40; + } + + if (blockSet.contains(GraphicBlock.class)){ + mask |= 0x200; + } + + if (blockSet.contains(AppearanceBlock.class)) { + mask |= 0x4; + } + + if (blockSet.contains(TurnToPositionBlock.class)) { + mask |= 0x2; + } + + if (mask >= 0x100) { + mask |= 0x20; + blockBuilder.put(DataType.SHORT, DataOrder.LITTLE, mask); + } else { + blockBuilder.put(DataType.BYTE, mask); + } + + if (blockSet.contains(AnimationBlock.class)){ + putAnimationBlock(blockSet.get(AnimationBlock.class), blockBuilder); + } + + if (blockSet.contains(ChatBlock.class)) { + putChatBlock(blockSet.get(ChatBlock.class), blockBuilder); + } + + if (blockSet.contains(GraphicBlock.class)) { + putGraphicBlock(blockSet.get(GraphicBlock.class), blockBuilder); + } + + if (blockSet.contains(AppearanceBlock.class)) { + putAppearanceBlock(blockSet.get(AppearanceBlock.class), blockBuilder); + } + + if (blockSet.contains(TurnToPositionBlock.class)) { + putTurnToPositionBlock(blockSet.get(TurnToPositionBlock.class), blockBuilder); + } + } + } + + /** + * Puts a turn to position block into the specified builder. + * @param block The block. + * @param blockBuilder The builder. + */ + private void putTurnToPositionBlock(TurnToPositionBlock block, GamePacketBuilder blockBuilder) { + Position pos = block.getPosition(); + blockBuilder.put(DataType.SHORT, pos.getX() * 2 + 1); + blockBuilder.put(DataType.SHORT, pos.getY() * 2 + 1); + } + + /** + * Puts a graphic block into the specified builder. + * @param block The block. + * @param blockBuilder The builder. + */ + private void putGraphicBlock(GraphicBlock block, GamePacketBuilder blockBuilder) { + Graphic graphic = block.getGraphic(); + blockBuilder.put(DataType.SHORT, DataTransformation.ADD, graphic.getId()); + blockBuilder.put(DataType.INT, DataOrder.MIDDLE, ((graphic.getHeight() << 16) & 0xFFFF0000) | (graphic.getDelay() & 0x0000FFFF)); + } + + /** + * Puts an animation block into the specified builder. + * @param block The block. + * @param blockBuilder The builder. + */ + private void putAnimationBlock(AnimationBlock block, GamePacketBuilder blockBuilder) { + Animation animation = block.getAnimation(); + blockBuilder.put(DataType.SHORT, animation.getId()); + blockBuilder.put(DataType.BYTE, DataTransformation.ADD, animation.getDelay()); + } + + /** + * Puts a chat block into the specified builder. + * @param block The block. + * @param blockBuilder The builder. + */ + private void putChatBlock(ChatBlock block, GamePacketBuilder blockBuilder) { + byte[] bytes = block.getCompressedMessage(); + blockBuilder.put(DataType.SHORT, DataOrder.LITTLE, (block.getTextEffects() << 8) | block.getTextColor()); + blockBuilder.put(DataType.BYTE, DataTransformation.NEGATE, block.getPrivilegeLevel().toInteger()); + blockBuilder.put(DataType.BYTE, DataTransformation.ADD, bytes.length); + blockBuilder.putBytes(DataTransformation.ADD, bytes); + } + + /** + * Puts an appearance block into the specified builder. + * @param block The block. + * @param blockBuilder The builder. + */ + private void putAppearanceBlock(AppearanceBlock block, GamePacketBuilder blockBuilder) { + Appearance appearance = block.getAppearance(); + GamePacketBuilder playerProperties = new GamePacketBuilder(); + + playerProperties.put(DataType.BYTE, appearance.getGender().toInteger()); // gender + playerProperties.put(DataType.BYTE, -1); // skull icon + playerProperties.put(DataType.BYTE, -1); // prayer icon + + Inventory equipment = block.getEquipment(); + int[] style = appearance.getStyle(); + Item item, chest, helm; + + for (int slot = 0; slot < 4; slot++) { + if ((item = equipment.get(slot)) != null) { + playerProperties.put(DataType.SHORT, 0x200 + item.getId()); + } else { + playerProperties.put(DataType.BYTE, 0); + } + } + + if ((chest = equipment.get(EquipmentConstants.CHEST)) != null) { + playerProperties.put(DataType.SHORT, 0x200 + chest.getId()); + } else { + playerProperties.put(DataType.SHORT, 0x100 + style[2]); + } + + if ((item = equipment.get(EquipmentConstants.SHIELD)) != null) { + playerProperties.put(DataType.SHORT, 0x200 + item.getId()); + } else { + playerProperties.put(DataType.BYTE, 0); + } + + if (chest != null) { + EquipmentDefinition def = EquipmentDefinition.forId(chest.getId()); + if (def != null && !def.isFullBody()) { + playerProperties.put(DataType.SHORT, 0x100 + style[3]); + } else { + playerProperties.put(DataType.BYTE, 0); + } + } else { + playerProperties.put(DataType.SHORT, 0x100 + style[3]); + } + + if ((item = equipment.get(EquipmentConstants.LEGS)) != null) { + playerProperties.put(DataType.SHORT, 0x200 + item.getId()); + } else { + playerProperties.put(DataType.SHORT, 0x100 + style[5]); + } + + if ((helm = equipment.get(EquipmentConstants.HAT)) != null) { + EquipmentDefinition def = EquipmentDefinition.forId(helm.getId()); + if (def != null && !def.isFullHat() && !def.isFullMask()) { + playerProperties.put(DataType.SHORT, 0x100 + style[0]); + } else { + playerProperties.put(DataType.BYTE, 0); + } + } else { + playerProperties.put(DataType.SHORT, 0x100 + style[0]); + } + + if ((item = equipment.get(EquipmentConstants.HANDS)) != null) { + playerProperties.put(DataType.SHORT, 0x200 + item.getId()); + } else { + playerProperties.put(DataType.SHORT, 0x100 + style[4]); + } + + if ((item = equipment.get(EquipmentConstants.FEET)) != null) { + playerProperties.put(DataType.SHORT, 0x200 + item.getId()); + } else { + playerProperties.put(DataType.SHORT, 0x100 + style[6]); + } + + EquipmentDefinition def = null; + if (helm != null) { + def = EquipmentDefinition.forId(helm.getId()); + } + if ((def != null && (def.isFullHat() || def.isFullMask())) || appearance.getGender() == Gender.FEMALE) { + playerProperties.put(DataType.BYTE, 0); + } else { + playerProperties.put(DataType.SHORT, 0x100 + style[1]); + } + + int[] colors = appearance.getColors(); + for (int i = 0; i < colors.length; i++) { + playerProperties.put(DataType.BYTE, colors[i]); + } + + playerProperties.put(DataType.SHORT, 0x328); // stand + playerProperties.put(DataType.SHORT, 0x337); // stand turn + playerProperties.put(DataType.SHORT, 0x333); // walk + playerProperties.put(DataType.SHORT, 0x334); // turn 180 + playerProperties.put(DataType.SHORT, 0x335); // turn 90 cw + playerProperties.put(DataType.SHORT, 0x336); // turn 90 ccw + playerProperties.put(DataType.SHORT, 0x338); // run + + playerProperties.put(DataType.LONG, block.getName()); + playerProperties.put(DataType.BYTE, block.getCombatLevel()); // combat level + playerProperties.put(DataType.SHORT, block.getSkillLevel()); // total skill level + + blockBuilder.put(DataType.BYTE, playerProperties.getLength()); + blockBuilder.putRawBuilderReverse(playerProperties); + } + +} diff --git a/src/org/apollo/net/release/r377/RegionChangeEventEncoder.java b/src/org/apollo/net/release/r377/RegionChangeEventEncoder.java new file mode 100644 index 000000000..741be5500 --- /dev/null +++ b/src/org/apollo/net/release/r377/RegionChangeEventEncoder.java @@ -0,0 +1,25 @@ +package org.apollo.net.release.r377; + +import org.apollo.game.event.impl.RegionChangeEvent; +import org.apollo.net.codec.game.DataOrder; +import org.apollo.net.codec.game.DataTransformation; +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.EventEncoder; + +/** + * An {@link EventEncoder} for the {@link RegionChangeEvent}. + * @author Graham + */ +public final class RegionChangeEventEncoder extends EventEncoder { + + @Override + public GamePacket encode(RegionChangeEvent event) { + GamePacketBuilder builder = new GamePacketBuilder(222); + builder.put(DataType.SHORT, event.getPosition().getCentralRegionY()); + builder.put(DataType.SHORT, DataOrder.LITTLE, DataTransformation.ADD, event.getPosition().getCentralRegionX()); + return builder.toGamePacket(); + } + +} diff --git a/src/org/apollo/net/release/r377/Release377.java b/src/org/apollo/net/release/r377/Release377.java new file mode 100644 index 000000000..282baa606 --- /dev/null +++ b/src/org/apollo/net/release/r377/Release377.java @@ -0,0 +1,170 @@ +package org.apollo.net.release.r377; + +import org.apollo.game.event.impl.CloseInterfaceEvent; +import org.apollo.game.event.impl.EnterAmountEvent; +import org.apollo.game.event.impl.IdAssignmentEvent; +import org.apollo.game.event.impl.LogoutEvent; +import org.apollo.game.event.impl.OpenInterfaceEvent; +import org.apollo.game.event.impl.OpenInterfaceSidebarEvent; +import org.apollo.game.event.impl.PlayerSynchronizationEvent; +import org.apollo.game.event.impl.RegionChangeEvent; +import org.apollo.game.event.impl.ServerMessageEvent; +import org.apollo.game.event.impl.SetInterfaceTextEvent; +import org.apollo.game.event.impl.SwitchTabInterfaceEvent; +import org.apollo.game.event.impl.UpdateItemsEvent; +import org.apollo.game.event.impl.UpdateSkillEvent; +import org.apollo.game.event.impl.UpdateSlottedItemsEvent; +import org.apollo.net.meta.PacketMetaDataGroup; +import org.apollo.net.release.Release; + +/** + * An implementation of {@link Release} for the 377 protocol. + * @author Graham + */ +public final class Release377 extends Release { + + /** + * The incoming packet lengths array. + */ + public static final int[] PACKET_LENGTHS = new int[256]; + + /** + * Initialises the {@link #PACKET_LENGTHS} array. + * TODO make it like the 317 one. + */ + static { + PACKET_LENGTHS[1] = 12; + PACKET_LENGTHS[3] = 6; + PACKET_LENGTHS[4] = 6; + PACKET_LENGTHS[6] = 0; + PACKET_LENGTHS[8] = 2; + PACKET_LENGTHS[13] = 2; + PACKET_LENGTHS[19] = 4; + PACKET_LENGTHS[22] = 2; + PACKET_LENGTHS[24] = 6; + PACKET_LENGTHS[28] = -1; + PACKET_LENGTHS[31] = 4; + PACKET_LENGTHS[36] = 8; + PACKET_LENGTHS[40] = 0; + PACKET_LENGTHS[42] = 2; + PACKET_LENGTHS[45] = 2; + PACKET_LENGTHS[49] = -1; + PACKET_LENGTHS[50] = 6; + PACKET_LENGTHS[54] = 6; + PACKET_LENGTHS[55] = 6; + PACKET_LENGTHS[56] = -1; + PACKET_LENGTHS[57] = 8; + PACKET_LENGTHS[67] = 2; + PACKET_LENGTHS[71] = 6; + PACKET_LENGTHS[75] = 4; + PACKET_LENGTHS[77] = 6; + PACKET_LENGTHS[78] = 4; + PACKET_LENGTHS[79] = 2; + PACKET_LENGTHS[80] = 2; + PACKET_LENGTHS[83] = 8; + PACKET_LENGTHS[91] = 6; + PACKET_LENGTHS[95] = 4; + PACKET_LENGTHS[100] = 6; + PACKET_LENGTHS[104] = 4; + PACKET_LENGTHS[110] = 0; + PACKET_LENGTHS[112] = 2; + PACKET_LENGTHS[116] = 2; + PACKET_LENGTHS[119] = 1; + PACKET_LENGTHS[120] = 8; + PACKET_LENGTHS[123] = 7; + PACKET_LENGTHS[126] = 1; + PACKET_LENGTHS[136] = 6; + PACKET_LENGTHS[140] = 4; + PACKET_LENGTHS[141] = 8; + PACKET_LENGTHS[143] = 8; + PACKET_LENGTHS[152] = 12; + PACKET_LENGTHS[157] = 4; + PACKET_LENGTHS[158] = 6; + PACKET_LENGTHS[160] = 8; + PACKET_LENGTHS[161] = 6; + PACKET_LENGTHS[163] = 13; + PACKET_LENGTHS[165] = 1; + PACKET_LENGTHS[168] = 0; + PACKET_LENGTHS[171] = -1; + PACKET_LENGTHS[173] = 3; + PACKET_LENGTHS[176] = 3; + PACKET_LENGTHS[177] = 6; + PACKET_LENGTHS[181] = 6; + PACKET_LENGTHS[184] = 10; + PACKET_LENGTHS[187] = 1; + PACKET_LENGTHS[194] = 2; + PACKET_LENGTHS[197] = 4; + PACKET_LENGTHS[202] = 0; + PACKET_LENGTHS[203] = 6; + PACKET_LENGTHS[206] = 8; + PACKET_LENGTHS[210] = 8; + PACKET_LENGTHS[211] = 12; + PACKET_LENGTHS[213] = -1; + PACKET_LENGTHS[217] = 8; + PACKET_LENGTHS[222] = 3; + PACKET_LENGTHS[226] = 2; + PACKET_LENGTHS[227] = 9; + PACKET_LENGTHS[228] = 6; + PACKET_LENGTHS[230] = 6; + PACKET_LENGTHS[231] = 6; + PACKET_LENGTHS[233] = 2; + PACKET_LENGTHS[241] = 6; + PACKET_LENGTHS[244] = -1; + PACKET_LENGTHS[245] = 2; + PACKET_LENGTHS[247] = -1; + PACKET_LENGTHS[248] = 0; + } + + /** + * Creates and initialises this release. + */ + public Release377() { + super(377, PacketMetaDataGroup.createFromArray(PACKET_LENGTHS)); + init(); + } + + /** + * Initialises this release by registering encoders and decoders. + */ + private void init() { + // register decoders + register(248, new KeepAliveEventDecoder()); + register(163, new CharacterDesignEventDecoder()); + WalkEventDecoder walkEventDecoder = new WalkEventDecoder(); + register(213, walkEventDecoder); + register(28, walkEventDecoder); + register(247, walkEventDecoder); + register(49, new ChatEventDecoder()); + register(79, new ButtonEventDecoder()); + register(56, new CommandEventDecoder()); + register(123, new SwitchItemEventDecoder()); + register(181, new FirstObjectActionEventDecoder()); + register(241, new SecondObjectActionEventDecoder()); + register(50, new ThirdObjectActionEventDecoder()); + register(24, new EquipEventDecoder()); + register(3, new FirstItemActionEventDecoder()); + register(177, new SecondItemActionEventDecoder()); + register(91, new ThirdItemActionEventDecoder()); + register(231, new FourthItemActionEventDecoder()); + register(158, new FifthItemActionEventDecoder()); + register(110, new ClosedInterfaceEventDecoder()); + register(75, new EnteredAmountEventDecoder()); + + // register encoders + register(IdAssignmentEvent.class, new IdAssignmentEventEncoder()); + register(RegionChangeEvent.class, new RegionChangeEventEncoder()); + register(ServerMessageEvent.class, new ServerMessageEventEncoder()); + register(PlayerSynchronizationEvent.class, new PlayerSynchronizationEventEncoder()); + register(OpenInterfaceEvent.class, new OpenInterfaceEventEncoder()); + register(CloseInterfaceEvent.class, new CloseInterfaceEventEncoder()); + register(SwitchTabInterfaceEvent.class, new SwitchTabInterfaceEventEncoder()); + register(LogoutEvent.class, new LogoutEventEncoder()); + register(UpdateItemsEvent.class, new UpdateItemsEventEncoder()); + register(UpdateSlottedItemsEvent.class, new UpdateSlottedItemsEventEncoder()); + register(UpdateSkillEvent.class, new UpdateSkillEventEncoder()); + register(OpenInterfaceSidebarEvent.class, new OpenInterfaceSidebarEventEncoder()); + register(EnterAmountEvent.class, new EnterAmountEventEncoder()); + register(SetInterfaceTextEvent.class, new SetInterfaceTextEventEncoder()); + } + +} diff --git a/src/org/apollo/net/release/r377/SecondItemActionEventDecoder.java b/src/org/apollo/net/release/r377/SecondItemActionEventDecoder.java new file mode 100644 index 000000000..e3bf7d2e3 --- /dev/null +++ b/src/org/apollo/net/release/r377/SecondItemActionEventDecoder.java @@ -0,0 +1,26 @@ +package org.apollo.net.release.r377; + +import org.apollo.game.event.impl.SecondItemActionEvent; +import org.apollo.net.codec.game.DataOrder; +import org.apollo.net.codec.game.DataTransformation; +import org.apollo.net.codec.game.DataType; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketReader; +import org.apollo.net.release.EventDecoder; + +/** + * An {@link EventDecoder} for the {@link SecondItemActionEvent}. + * @author Graham + */ +public final class SecondItemActionEventDecoder extends EventDecoder { + + @Override + public SecondItemActionEvent decode(GamePacket packet) { + GamePacketReader reader = new GamePacketReader(packet); + int slot = (int) reader.getUnsigned(DataType.SHORT, DataTransformation.ADD); + int id = (int) reader.getUnsigned(DataType.SHORT, DataOrder.LITTLE); + int interfaceId = (int) reader.getUnsigned(DataType.SHORT, DataOrder.LITTLE); + return new SecondItemActionEvent(interfaceId, id, slot); + } + +} diff --git a/src/org/apollo/net/release/r377/SecondObjectActionEventDecoder.java b/src/org/apollo/net/release/r377/SecondObjectActionEventDecoder.java new file mode 100644 index 000000000..4e5309bf6 --- /dev/null +++ b/src/org/apollo/net/release/r377/SecondObjectActionEventDecoder.java @@ -0,0 +1,26 @@ +package org.apollo.net.release.r377; + +import org.apollo.game.event.impl.SecondObjectActionEvent; +import org.apollo.game.model.Position; +import org.apollo.net.codec.game.DataTransformation; +import org.apollo.net.codec.game.DataType; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketReader; +import org.apollo.net.release.EventDecoder; + +/** + * An {@link EventDecoder} for the {@link SecondObjectActionEvent}. + * @author Graham + */ +public final class SecondObjectActionEventDecoder extends EventDecoder { + + @Override + public SecondObjectActionEvent decode(GamePacket packet) { + GamePacketReader reader = new GamePacketReader(packet); + int id = (int) reader.getUnsigned(DataType.SHORT); + int x = (int) reader.getUnsigned(DataType.SHORT); + int y = (int) reader.getUnsigned(DataType.SHORT, DataTransformation.ADD); + return new SecondObjectActionEvent(id, new Position(x, y)); + } + +} diff --git a/src/org/apollo/net/release/r377/ServerMessageEventEncoder.java b/src/org/apollo/net/release/r377/ServerMessageEventEncoder.java new file mode 100644 index 000000000..6c87728bb --- /dev/null +++ b/src/org/apollo/net/release/r377/ServerMessageEventEncoder.java @@ -0,0 +1,22 @@ +package org.apollo.net.release.r377; + +import org.apollo.game.event.impl.ServerMessageEvent; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketBuilder; +import org.apollo.net.meta.PacketType; +import org.apollo.net.release.EventEncoder; + +/** + * An {@link EventEncoder} for the {@link ServerMessageEvent}. + * @author Graham + */ +public final class ServerMessageEventEncoder extends EventEncoder { + + @Override + public GamePacket encode(ServerMessageEvent event) { + GamePacketBuilder builder = new GamePacketBuilder(63, PacketType.VARIABLE_BYTE); + builder.putString(event.getMessage()); + return builder.toGamePacket(); + } + +} diff --git a/src/org/apollo/net/release/r377/SetInterfaceTextEventEncoder.java b/src/org/apollo/net/release/r377/SetInterfaceTextEventEncoder.java new file mode 100644 index 000000000..5b2c267e9 --- /dev/null +++ b/src/org/apollo/net/release/r377/SetInterfaceTextEventEncoder.java @@ -0,0 +1,26 @@ +package org.apollo.net.release.r377; + +import org.apollo.game.event.impl.SetInterfaceTextEvent; +import org.apollo.net.codec.game.DataOrder; +import org.apollo.net.codec.game.DataTransformation; +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.meta.PacketType; +import org.apollo.net.release.EventEncoder; + +/** + * An {@link EventEncoder} for the {@link SetInterfaceTextEvent}. + * @author Graham + */ +public final class SetInterfaceTextEventEncoder extends EventEncoder { + + @Override + public GamePacket encode(SetInterfaceTextEvent event) { + GamePacketBuilder builder = new GamePacketBuilder(232, PacketType.VARIABLE_SHORT); + builder.put(DataType.SHORT, DataOrder.LITTLE, DataTransformation.ADD, event.getInterfaceId()); + builder.putString(event.getText()); + return builder.toGamePacket(); + } + +} diff --git a/src/org/apollo/net/release/r377/SwitchItemEventDecoder.java b/src/org/apollo/net/release/r377/SwitchItemEventDecoder.java new file mode 100644 index 000000000..f56285ffb --- /dev/null +++ b/src/org/apollo/net/release/r377/SwitchItemEventDecoder.java @@ -0,0 +1,27 @@ +package org.apollo.net.release.r377; + +import org.apollo.game.event.impl.SwitchItemEvent; +import org.apollo.net.codec.game.DataOrder; +import org.apollo.net.codec.game.DataTransformation; +import org.apollo.net.codec.game.DataType; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketReader; +import org.apollo.net.release.EventDecoder; + +/** + * An {@link EventDecoder} for the {@link SwitchItemEvent}. + * @author Graham + */ +public final class SwitchItemEventDecoder extends EventDecoder { + + @Override + public SwitchItemEvent decode(GamePacket packet) { + GamePacketReader reader = new GamePacketReader(packet); + int newSlot = (int) reader.getUnsigned(DataType.SHORT, DataOrder.LITTLE, DataTransformation.ADD); + boolean inserting = reader.getUnsigned(DataType.BYTE, DataTransformation.ADD) == 1; + int interfaceId = (int) reader.getUnsigned(DataType.SHORT, DataTransformation.ADD); + int oldSlot = (int) reader.getUnsigned(DataType.SHORT, DataOrder.LITTLE); + return new SwitchItemEvent(interfaceId, inserting, oldSlot, newSlot); + } + +} diff --git a/src/org/apollo/net/release/r377/SwitchTabInterfaceEventEncoder.java b/src/org/apollo/net/release/r377/SwitchTabInterfaceEventEncoder.java new file mode 100644 index 000000000..d6ab593d0 --- /dev/null +++ b/src/org/apollo/net/release/r377/SwitchTabInterfaceEventEncoder.java @@ -0,0 +1,24 @@ +package org.apollo.net.release.r377; + +import org.apollo.game.event.impl.SwitchTabInterfaceEvent; +import org.apollo.net.codec.game.DataTransformation; +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.EventEncoder; + +/** + * An {@link EventEncoder} for the {@link SwitchTabInterfaceEvent}. + * @author Graham + */ +public final class SwitchTabInterfaceEventEncoder extends EventEncoder { + + @Override + public GamePacket encode(SwitchTabInterfaceEvent event) { + GamePacketBuilder builder = new GamePacketBuilder(10); + builder.put(DataType.BYTE, DataTransformation.SUBTRACT, event.getTabId()); + builder.put(DataType.SHORT, DataTransformation.ADD, event.getInterfaceId()); + return builder.toGamePacket(); + } + +} diff --git a/src/org/apollo/net/release/r377/ThirdItemActionEventDecoder.java b/src/org/apollo/net/release/r377/ThirdItemActionEventDecoder.java new file mode 100644 index 000000000..5ba0c10f9 --- /dev/null +++ b/src/org/apollo/net/release/r377/ThirdItemActionEventDecoder.java @@ -0,0 +1,26 @@ +package org.apollo.net.release.r377; + +import org.apollo.game.event.impl.ThirdItemActionEvent; +import org.apollo.net.codec.game.DataOrder; +import org.apollo.net.codec.game.DataTransformation; +import org.apollo.net.codec.game.DataType; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketReader; +import org.apollo.net.release.EventDecoder; + +/** + * An {@link EventDecoder} for the {@link ThirdItemActionEvent}. + * @author Graham + */ +public final class ThirdItemActionEventDecoder extends EventDecoder { + + @Override + public ThirdItemActionEvent decode(GamePacket packet) { + GamePacketReader reader = new GamePacketReader(packet); + int id = (int) reader.getUnsigned(DataType.SHORT, DataOrder.LITTLE); + int slot = (int) reader.getUnsigned(DataType.SHORT, DataOrder.LITTLE, DataTransformation.ADD); + int interfaceId = (int) reader.getUnsigned(DataType.SHORT); + return new ThirdItemActionEvent(interfaceId, id, slot); + } + +} diff --git a/src/org/apollo/net/release/r377/ThirdObjectActionEventDecoder.java b/src/org/apollo/net/release/r377/ThirdObjectActionEventDecoder.java new file mode 100644 index 000000000..dedd3cbb7 --- /dev/null +++ b/src/org/apollo/net/release/r377/ThirdObjectActionEventDecoder.java @@ -0,0 +1,27 @@ +package org.apollo.net.release.r377; + +import org.apollo.game.event.impl.ThirdObjectActionEvent; +import org.apollo.game.model.Position; +import org.apollo.net.codec.game.DataOrder; +import org.apollo.net.codec.game.DataTransformation; +import org.apollo.net.codec.game.DataType; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketReader; +import org.apollo.net.release.EventDecoder; + +/** + * An {@link EventDecoder} for the {@link ThirdObjectActionEvent}. + * @author Graham + */ +public final class ThirdObjectActionEventDecoder extends EventDecoder { + + @Override + public ThirdObjectActionEvent decode(GamePacket packet) { + GamePacketReader reader = new GamePacketReader(packet); + int y = (int) reader.getUnsigned(DataType.SHORT, DataTransformation.ADD); + int x = (int) reader.getUnsigned(DataType.SHORT, DataOrder.LITTLE); + int id = (int) reader.getUnsigned(DataType.SHORT, DataOrder.LITTLE, DataTransformation.ADD); + return new ThirdObjectActionEvent(id, new Position(x, y)); + } + +} diff --git a/src/org/apollo/net/release/r377/UpdateItemsEventEncoder.java b/src/org/apollo/net/release/r377/UpdateItemsEventEncoder.java new file mode 100644 index 000000000..21e245550 --- /dev/null +++ b/src/org/apollo/net/release/r377/UpdateItemsEventEncoder.java @@ -0,0 +1,46 @@ +package org.apollo.net.release.r377; + +import org.apollo.game.event.impl.UpdateItemsEvent; +import org.apollo.game.model.Item; +import org.apollo.net.codec.game.DataOrder; +import org.apollo.net.codec.game.DataTransformation; +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.meta.PacketType; +import org.apollo.net.release.EventEncoder; + +/** + * An {@link EventEncoder} for the {@link UpdateItemsEvent}. + * @author Graham + */ +public final class UpdateItemsEventEncoder extends EventEncoder { + + @Override + public GamePacket encode(UpdateItemsEvent event) { + GamePacketBuilder builder = new GamePacketBuilder(206, PacketType.VARIABLE_SHORT); + + Item[] items = event.getItems(); + + builder.put(DataType.SHORT, event.getInterfaceId()); + builder.put(DataType.SHORT, items.length); + + for (int i = 0; i < items.length; i++) { + Item item = items[i]; + int id = item == null ? -1 : item.getId(); + int amount = item == null ? 0 : item.getAmount(); + + builder.put(DataType.SHORT, DataOrder.LITTLE, DataTransformation.ADD, id + 1); + + if (amount > 254) { + builder.put(DataType.BYTE, DataTransformation.NEGATE, 255); + builder.put(DataType.INT, DataOrder.LITTLE, amount); + } else { + builder.put(DataType.BYTE, DataTransformation.NEGATE, amount); + } + } + + return builder.toGamePacket(); + } + +} diff --git a/src/org/apollo/net/release/r377/UpdateSkillEventEncoder.java b/src/org/apollo/net/release/r377/UpdateSkillEventEncoder.java new file mode 100644 index 000000000..d039c80e3 --- /dev/null +++ b/src/org/apollo/net/release/r377/UpdateSkillEventEncoder.java @@ -0,0 +1,29 @@ +package org.apollo.net.release.r377; + +import org.apollo.game.event.impl.UpdateSkillEvent; +import org.apollo.game.model.Skill; +import org.apollo.net.codec.game.DataTransformation; +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.EventEncoder; + +/** + * An {@link EventEncoder} for the {@link UpdateSkillEvent}. + * @author Graham + */ +public final class UpdateSkillEventEncoder extends EventEncoder { + + @Override + public GamePacket encode(UpdateSkillEvent event) { + GamePacketBuilder builder = new GamePacketBuilder(49); + Skill skill = event.getSkill(); + + builder.put(DataType.BYTE, DataTransformation.NEGATE, event.getId()); + builder.put(DataType.BYTE, skill.getCurrentLevel()); + builder.put(DataType.INT, (int) skill.getExperience()); + + return builder.toGamePacket(); + } + +} diff --git a/src/org/apollo/net/release/r377/UpdateSlottedItemsEventEncoder.java b/src/org/apollo/net/release/r377/UpdateSlottedItemsEventEncoder.java new file mode 100644 index 000000000..2906e6d41 --- /dev/null +++ b/src/org/apollo/net/release/r377/UpdateSlottedItemsEventEncoder.java @@ -0,0 +1,45 @@ +package org.apollo.net.release.r377; + +import org.apollo.game.event.impl.UpdateSlottedItemsEvent; +import org.apollo.game.model.Item; +import org.apollo.game.model.SlottedItem; +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.meta.PacketType; +import org.apollo.net.release.EventEncoder; + +/** + * An {@link EventEncoder} for the {@link UpdateSlottedItemsEvent}. + * @author Graham + */ +public final class UpdateSlottedItemsEventEncoder extends EventEncoder { + + @Override + public GamePacket encode(UpdateSlottedItemsEvent event) { + GamePacketBuilder builder = new GamePacketBuilder(134, PacketType.VARIABLE_SHORT); + SlottedItem[] items = event.getSlottedItems(); + + builder.put(DataType.SHORT, event.getInterfaceId()); + + for (SlottedItem slottedItem : items) { + builder.putSmart(slottedItem.getSlot()); + + Item item = slottedItem.getItem(); + int id = item == null ? -1 : item.getId(); + int amount = item == null ? 0 : item.getAmount(); + + builder.put(DataType.SHORT, id + 1); + + if (amount > 254) { + builder.put(DataType.BYTE, 255); + builder.put(DataType.INT, amount); + } else { + builder.put(DataType.BYTE, amount); + } + } + + return builder.toGamePacket(); + } + +} diff --git a/src/org/apollo/net/release/r377/WalkEventDecoder.java b/src/org/apollo/net/release/r377/WalkEventDecoder.java new file mode 100644 index 000000000..ea637fda9 --- /dev/null +++ b/src/org/apollo/net/release/r377/WalkEventDecoder.java @@ -0,0 +1,48 @@ +package org.apollo.net.release.r377; + +import org.apollo.game.event.impl.WalkEvent; +import org.apollo.game.model.Position; +import org.apollo.net.codec.game.DataOrder; +import org.apollo.net.codec.game.DataTransformation; +import org.apollo.net.codec.game.DataType; +import org.apollo.net.codec.game.GamePacket; +import org.apollo.net.codec.game.GamePacketReader; +import org.apollo.net.release.EventDecoder; + +/** + * An {@link EventDecoder} for the {@link WalkEvent}. + * @author Graham + */ +public final class WalkEventDecoder extends EventDecoder { + + @Override + public WalkEvent decode(GamePacket packet) { + GamePacketReader reader = new GamePacketReader(packet); + + int length = packet.getLength(); + if (packet.getOpcode() == 213) { + length -= 14; // strip off anti-cheat data + } + + int steps = (length - 5) / 2; + int[][] path = new int[steps][2]; + + int x = (int) reader.getUnsigned(DataType.SHORT, DataOrder.LITTLE, DataTransformation.ADD); + boolean run = reader.getUnsigned(DataType.BYTE) == 1; + int y = (int) reader.getUnsigned(DataType.SHORT, DataOrder.LITTLE, DataTransformation.ADD); + + for (int i = 0; i < steps; i++) { + path[i][0] = (int) reader.getSigned(DataType.BYTE); + path[i][1] = (int) reader.getSigned(DataType.BYTE, DataTransformation.SUBTRACT); + } + + Position[] positions = new Position[steps + 1]; + positions[0] = new Position(x, y); + for (int i = 0; i < steps; i++) { + positions[i + 1] = new Position(path[i][0] + x, path[i][1] + y); + } + + return new WalkEvent(positions, run); + } + +} diff --git a/src/org/apollo/net/release/r377/package-info.java b/src/org/apollo/net/release/r377/package-info.java new file mode 100644 index 000000000..af4c5ef06 --- /dev/null +++ b/src/org/apollo/net/release/r377/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains codecs for the 377 release. + */ +package org.apollo.net.release.r377; diff --git a/src/org/apollo/net/session/GameSession.java b/src/org/apollo/net/session/GameSession.java new file mode 100644 index 000000000..9d06b95df --- /dev/null +++ b/src/org/apollo/net/session/GameSession.java @@ -0,0 +1,129 @@ +package org.apollo.net.session; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.apollo.ServerContext; +import org.apollo.game.GameConstants; +import org.apollo.game.GameService; +import org.apollo.game.event.Event; +import org.apollo.game.event.handler.chain.EventHandlerChainGroup; +import org.apollo.game.event.handler.chain.EventHandlerChain; +import org.apollo.game.event.impl.LogoutEvent; +import org.apollo.game.model.Player; +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelFuture; +import org.jboss.netty.channel.ChannelFutureListener; + +/** + * A game session. + * @author Graham + */ +public final class GameSession extends Session { + + /** + * The logger for this class. + */ + private static final Logger logger = Logger.getLogger(GameSession.class.getName()); + + /** + * The server context. + */ + private final ServerContext context; + + /** + * The queue of pending {@link Event}s. + */ + private final BlockingQueue eventQueue = new ArrayBlockingQueue(GameConstants.EVENTS_PER_PULSE); + + /** + * The player. + */ + private final Player player; + + /** + * Creates a login session for the specified channel. + * @param channel The channel. + * @param context The server context. + * @param player The player. + */ + public GameSession(Channel channel, ServerContext context, Player player) { + super(channel); + this.context = context; + this.player = player; + } + + @Override + public void messageReceived(Object message) throws Exception { + Event event = (Event) message; + if (eventQueue.size() >= GameConstants.EVENTS_PER_PULSE) { + logger.warning("Too many events in queue for game session, dropping..."); + } else { + eventQueue.add(event); + } + } + + /** + * Encodes and dispatches the specified event. + * @param event The event. + */ + public void dispatchEvent(Event event) { + Channel channel = getChannel(); + if (channel.isBound() && channel.isConnected() && channel.isOpen()) { + ChannelFuture future = channel.write(event); + if (event.getClass() == LogoutEvent.class) { + future.addListener(ChannelFutureListener.CLOSE); + } + } + } + + /** + * Handles pending events for this session. + * @param chainGroup The event chain group. + */ + @SuppressWarnings("unchecked") + public void handlePendingEvents(EventHandlerChainGroup chainGroup) { + Event event; + while ((event = eventQueue.poll()) != null) { + // this lookup code really sucks! + // TODO improve it! + Class eventType = event.getClass(); + EventHandlerChain chain = (EventHandlerChain) chainGroup.getChain(eventType); + + while (chain == null && eventType != null) { + eventType = (Class) eventType.getSuperclass(); + if (eventType == Event.class) { + eventType = null; + } else { + chain = (EventHandlerChain) chainGroup.getChain(eventType); + } + } + + if (chain == null) { + logger.warning("No chain for event: " + event.getClass().getName() + "."); + } else { + try { + chain.handle(player, event); + } catch (Exception ex) { + logger.log(Level.SEVERE, "Error handling event.", ex); + } + } + } + } + + /** + * Handles a player saver response. + * @param success A flag indicating if the save was successful. + */ + public void handlePlayerSaverResponse(boolean success) { + context.getService(GameService.class).finalizePlayerUnregistration(player); + } + + @Override + public void destroy() throws Exception { + context.getService(GameService.class).unregisterPlayer(player); + } + +} diff --git a/src/org/apollo/net/session/LoginSession.java b/src/org/apollo/net/session/LoginSession.java new file mode 100644 index 000000000..c5709723d --- /dev/null +++ b/src/org/apollo/net/session/LoginSession.java @@ -0,0 +1,135 @@ +package org.apollo.net.session; + +import org.apollo.ServerContext; +import org.apollo.game.GameService; +import org.apollo.game.model.Player; +import org.apollo.game.model.World.RegistrationStatus; +import org.apollo.io.player.PlayerLoaderResponse; +import org.apollo.login.LoginService; +import org.apollo.net.ApolloHandler; +import org.apollo.net.codec.game.GameEventDecoder; +import org.apollo.net.codec.game.GameEventEncoder; +import org.apollo.net.codec.game.GamePacketDecoder; +import org.apollo.net.codec.game.GamePacketEncoder; +import org.apollo.net.codec.login.LoginConstants; +import org.apollo.net.codec.login.LoginRequest; +import org.apollo.net.codec.login.LoginResponse; +import org.apollo.net.release.Release; +import org.apollo.security.IsaacRandomPair; +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelFuture; +import org.jboss.netty.channel.ChannelFutureListener; +import org.jboss.netty.channel.ChannelHandlerContext; + +/** + * A login session. + * @author Graham + */ +public final class LoginSession extends Session { + + /** + * The context of the {@link ApolloHandler}. + */ + private final ChannelHandlerContext channelContext; + + /** + * The server context. + */ + private final ServerContext serverContext; + + /** + * Creates a login session for the specified channel. + * @param channel The channel. + * @param channelContext The context of the {@link ApolloHandler}. + * @param serverContext The server context. + */ + public LoginSession(Channel channel, ChannelHandlerContext channelContext, ServerContext serverContext) { + super(channel); + this.channelContext = channelContext; + this.serverContext = serverContext; + } + + @Override + public void messageReceived(Object message) throws Exception { + if (message.getClass() == LoginRequest.class) { + handleLoginRequest((LoginRequest) message); + } + } + + /** + * Gets the release. + * @return The release. + */ + public Release getRelease() { + return serverContext.getRelease(); + } + + /** + * Handles a login request. + * @param request The login request. + */ + private void handleLoginRequest(LoginRequest request) { + LoginService loginService = serverContext.getService(LoginService.class); + loginService.submitLoadRequest(this, request); + } + + /** + * Handles a response from the login service. + * @param request The request this response corresponds to. + * @param response The response. + */ + public void handlePlayerLoaderResponse(LoginRequest request, PlayerLoaderResponse response) { + GameService gameService = serverContext.getService(GameService.class); + Channel channel = getChannel(); + + int status = response.getStatus(); + Player player = response.getPlayer(); + int rights = player == null ? 0 : player.getPrivilegeLevel().toInteger(); + boolean log = false; + + if (player != null) { + GameSession session = new GameSession(channel, serverContext, player); + player.setSession(session, false /* TODO */); + + RegistrationStatus registrationStatus = gameService.registerPlayer(player); + + if (registrationStatus != RegistrationStatus.OK) { + player = null; + if (registrationStatus == RegistrationStatus.ALREADY_ONLINE) { + status = LoginConstants.STATUS_ACCOUNT_ONLINE; + } else { + status = LoginConstants.STATUS_SERVER_FULL; + } + rights = 0; + } + } + + ChannelFuture future = channel.write(new LoginResponse(status, rights, log)); + + destroy(); + + if (player != null) { + IsaacRandomPair randomPair = request.getRandomPair(); + Release release = serverContext.getRelease(); + + channel.getPipeline().addFirst("eventEncoder", new GameEventEncoder(release)); + channel.getPipeline().addBefore("eventEncoder", "gameEncoder", new GamePacketEncoder(randomPair.getEncodingRandom())); + + channel.getPipeline().addBefore("handler", "gameDecoder", new GamePacketDecoder(randomPair.getDecodingRandom(), serverContext.getRelease())); + channel.getPipeline().addAfter("gameDecoder", "eventDecoder", new GameEventDecoder(release)); + + channel.getPipeline().remove("loginDecoder"); + channel.getPipeline().remove("loginEncoder"); + + channelContext.setAttachment(player.getSession()); + } else { + future.addListener(ChannelFutureListener.CLOSE); + } + } + + @Override + public void destroy() { + + } + +} diff --git a/src/org/apollo/net/session/Session.java b/src/org/apollo/net/session/Session.java new file mode 100644 index 000000000..fa6e9570e --- /dev/null +++ b/src/org/apollo/net/session/Session.java @@ -0,0 +1,47 @@ +package org.apollo.net.session; + +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelHandlerContext; + +/** + * A session which is used as the attachment of a {@link ChannelHandlerContext} + * in Netty. + * @author Graham + */ +public abstract class Session { + + /** + * The channel. + */ + private final Channel channel; + + /** + * Creates a session for the specified channel. + * @param channel The channel. + */ + public Session(Channel channel) { + this.channel = channel; + } + + /** + * Gets the channel. + * @return The channel. + */ + protected final Channel getChannel() { + return channel; + } + + /** + * Processes a message received from the channel. + * @param message The message. + * @throws Exception if an error occurs. + */ + public abstract void messageReceived(Object message) throws Exception; + + /** + * Destroys this session. + * @throws Exception if an error occurs. + */ + public abstract void destroy() throws Exception; + +} diff --git a/src/org/apollo/net/session/UpdateSession.java b/src/org/apollo/net/session/UpdateSession.java new file mode 100644 index 000000000..53b662a11 --- /dev/null +++ b/src/org/apollo/net/session/UpdateSession.java @@ -0,0 +1,51 @@ +package org.apollo.net.session; + +import org.apollo.ServerContext; +import org.apollo.net.codec.jaggrab.JagGrabRequest; +import org.apollo.net.codec.update.OnDemandRequest; +import org.apollo.update.UpdateDispatcher; +import org.apollo.update.UpdateService; +import org.jboss.netty.channel.Channel; +import org.jboss.netty.handler.codec.http.HttpRequest; + +/** + * An update session. + * @author Graham + */ +public final class UpdateSession extends Session { + + /** + * The server context. + */ + private final ServerContext context; + + /** + * Creates an update session for the specified channel. + * @param channel The channel. + * @param context The server context. + */ + public UpdateSession(Channel channel, ServerContext context) { + super(channel); + this.context = context; + } + + @Override + public void messageReceived(Object message) throws Exception { + UpdateDispatcher dispatcher = context.getService(UpdateService.class).getDispatcher(); + if (message instanceof OnDemandRequest) { + dispatcher.dispatch(getChannel(), (OnDemandRequest) message); + } else if (message instanceof JagGrabRequest) { + dispatcher.dispatch(getChannel(), (JagGrabRequest) message); + } else if (message instanceof HttpRequest) { + dispatcher.dispatch(getChannel(), (HttpRequest) message); + } else { + throw new Exception("unknown message type"); + } + } + + @Override + public void destroy() throws Exception { + // TODO implement + } + +} diff --git a/src/org/apollo/net/session/package-info.java b/src/org/apollo/net/session/package-info.java new file mode 100644 index 000000000..a78c2eb62 --- /dev/null +++ b/src/org/apollo/net/session/package-info.java @@ -0,0 +1,7 @@ +/** + * Contains {@link org.apollo.net.session.Session} classes which are the + * equivalent of Netty's {@link org.jboss.netty.channel.Channel}s but are + * designed for Apollo to use itself - unlike Netty's which are purely + * designed for networking. + */ +package org.apollo.net.session; diff --git a/src/org/apollo/package-info.java b/src/org/apollo/package-info.java new file mode 100644 index 000000000..841fa95af --- /dev/null +++ b/src/org/apollo/package-info.java @@ -0,0 +1,5 @@ +/** + * Contains core Apollo server classes such as service management and the + * server bootstrap class. + */ +package org.apollo; diff --git a/src/org/apollo/security/IsaacRandomPair.java b/src/org/apollo/security/IsaacRandomPair.java new file mode 100644 index 000000000..e5fa4066b --- /dev/null +++ b/src/org/apollo/security/IsaacRandomPair.java @@ -0,0 +1,49 @@ +package org.apollo.security; + +import net.burtleburtle.bob.rand.IsaacRandom; + +/** + * A pair of two {@link IsaacRandom} random number generators used as a stream + * cipher. One takes the role of an encoder for this endpoint, the other takes + * the role of a decoder for this endpoint. + * @author Graham + */ +public final class IsaacRandomPair { + + /** + * The random number generator used to encode data. + */ + private final IsaacRandom encodingRandom; + + /** + * The random number generator used to decode data. + */ + private final IsaacRandom decodingRandom; + + /** + * Creates the pair of random number generators. + * @param encodingRandom The random number generator used for encoding. + * @param decodingRandom The random number generator used for decoding. + */ + public IsaacRandomPair(IsaacRandom encodingRandom, IsaacRandom decodingRandom) { + this.encodingRandom = encodingRandom; + this.decodingRandom = decodingRandom; + } + + /** + * Gets the random number generator used for encoding. + * @return The random number generator used for encoding. + */ + public IsaacRandom getEncodingRandom() { + return encodingRandom; + } + + /** + * Gets the random number generator used for decoding. + * @return The random number generator used for decoding. + */ + public IsaacRandom getDecodingRandom() { + return decodingRandom; + } + +} diff --git a/src/org/apollo/security/PlayerCredentials.java b/src/org/apollo/security/PlayerCredentials.java new file mode 100644 index 000000000..d2e224c36 --- /dev/null +++ b/src/org/apollo/security/PlayerCredentials.java @@ -0,0 +1,92 @@ +package org.apollo.security; + +import org.apollo.util.NameUtil; + +/** + * Holds the credentials for a player. + * @author Graham + */ +public final class PlayerCredentials { + + /** + * The player's username. + */ + private final String username; + + /** + * The player's username encoded as a long. + */ + private final long encodedUsername; + + /** + * The player's password. + */ + private final String password; + + /** + * The hash of the player's username. + */ + private final int usernameHash; + + /** + * The computer's unique identifier. + */ + private final int uid; + + /** + * Creates a new {@link PlayerCredentials} object with the specified name, + * password and uid. + * @param username The player's username. + * @param password The player's password. + * @param usernameHash The hash of the player's username. + * @param uid The computer's uid. + */ + public PlayerCredentials(String username, String password, int usernameHash, int uid) { + this.username = username; + this.encodedUsername = NameUtil.encodeBase37(username); + this.password = password; + this.usernameHash = usernameHash; + this.uid = uid; + } + + /** + * Gets the player's username. + * @return The player's username. + */ + public String getUsername() { + return username; + } + + /** + * Gets the player's username encoded as a long. + * @return The username as encoded by {@link NameUtil#encodeBase37(String)}. + */ + public long getEncodedUsername() { + return encodedUsername; + } + + /** + * Gets the player's password. + * @return The player's password. + */ + public String getPassword() { + return password; + } + + /** + * Gets the username hash. + * @return The username hash. + */ + public int getUsernameHash() { + return usernameHash; + } + + /** + * Gets the computer's uid. + * @return The computer's uid. + */ + public int getUid() { + return uid; + } + +} diff --git a/src/org/apollo/security/package-info.java b/src/org/apollo/security/package-info.java new file mode 100644 index 000000000..90f9c39b8 --- /dev/null +++ b/src/org/apollo/security/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains classes related to security and cryptography. + */ +package org.apollo.security; diff --git a/src/org/apollo/tools/EquipmentConstants.java b/src/org/apollo/tools/EquipmentConstants.java new file mode 100644 index 000000000..51f74cd4d --- /dev/null +++ b/src/org/apollo/tools/EquipmentConstants.java @@ -0,0 +1,90 @@ +package org.apollo.tools; + +/** + * Contains equipment name constants. + * @author Graham + * @author Palidino76 + */ +public final class EquipmentConstants { + + /** + * Capes. + */ + public static final String[] CAPES = {"cape","Cape"}; + + /** + * Hats. + */ + public static final String[] HATS = {"helm","hood","coif","Coif","hat","partyhat","Hat","full helm (t)","full helm (g)","hat (t)","hat (g)","cav","boater","helmet","mask","Helm of neitiznot"}; + + /** + * Boots. + */ + public static final String[] BOOTS = {"boots","Boots"}; + + /** + * Gloves. + */ + public static final String[] GLOVES = {"gloves","gauntlets","Gloves","vambraces","vamb","bracers"}; + + /** + * Shields. + */ + public static final String[] SHIELDS = {"kiteshield","sq shield","Toktz-ket","books","book","kiteshield (t)","kiteshield (g)","kiteshield(h)","defender","shield"}; + + /** + * Amulets. + */ + public static final String[] AMULETS = {"amulet","necklace","Amulet of"}; + + /** + * Arrows. + */ + public static final String[] ARROWS = {"arrow","arrows","arrow(p)","arrow(+)","arrow(s)","bolt","Bolt rack","Opal bolts","Dragon bolts"}; + + /** + * Rings. + */ + public static final String[] RINGS = {"ring"}; + + /** + * Bodies. + */ + public static final String[] BODY = {"platebody","chainbody","robetop","leathertop","platemail","top","brassard","Robe top","body","platebody (t)","platebody (g)","body(g)","body_(g)","chestplate","torso","shirt"}; + + /** + * Legs. + */ + public static final String[] LEGS = {"platelegs","plateskirt","skirt","bottoms","chaps","platelegs (t)","platelegs (g)","bottom","skirt","skirt (g)","skirt (t)","chaps (g)","chaps (t)","tassets","legs","Flared trousers"}; + + /** + * Weapons. + */ + public static final String[] WEAPONS = {"scimitar","longsword","sword","longbow","shortbow","dagger","mace","halberd","spear", + "Abyssal whip","axe","flail","crossbow","Torags hammers","dagger(p)","dagger(+)","dagger(s)","spear(p)","spear(+)", + "spear(s)","spear(kp)","maul","dart","dart(p)","javelin","javelin(p)","knife","knife(p)","Longbow","Shortbow", + "Crossbow","Toktz-xil","Toktz-mej","Tzhaar-ket","staff","Staff","godsword","c'bow","Crystal bow","Dark bow", "Magic butterfly net"}; + + /** + * Full bodies. + */ + public static final String[] FULL_BODIES = {"top","shirt","platebody","Ahrims robetop","Karils leathertop","brassard","Robe top","robetop","platebody (t)","platebody (g)","chestplate","torso"}; + + /** + * Full hats. + */ + public static final String[] FULL_HATS = {"med helm","coif","Dharoks helm","hood","Initiate helm","Coif","Helm of neitiznot"}; + + /** + * Full masks. + */ + public static final String[] FULL_MASKS = {"full helm","mask","Veracs helm","Guthans helm","Torags helm","Karils coif","full helm (t)","full helm (g)","mask"}; + + /** + * Default private construcotr to prevent instantiation. + */ + private EquipmentConstants() { + + } + +} diff --git a/src/org/apollo/tools/EquipmentUpdater.java b/src/org/apollo/tools/EquipmentUpdater.java new file mode 100644 index 000000000..e6b46edad --- /dev/null +++ b/src/org/apollo/tools/EquipmentUpdater.java @@ -0,0 +1,886 @@ +package org.apollo.tools; + +import java.io.BufferedOutputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileOutputStream; + +import org.apollo.fs.IndexedFileSystem; +import org.apollo.fs.parser.ItemDefinitionParser; +import org.apollo.game.model.def.ItemDefinition; + +/** + * A tool for updating the equipment data. + * @author Graham + * @author Palidino76 + */ +public final class EquipmentUpdater { + + /** + * The entry point of the application. + * @param args The command line arguments. + * @throws Exception if an error occurs. + */ + public static void main(String[] args) throws Exception { + if (args.length != 1) { + System.err.println("Usage:"); + System.err.println(" java -cp ... org.apollo.tools.EquipmentUpdater [release]"); + return; + } + String release = args[0]; + + DataOutputStream os = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("data/equipment-" + release + ".dat"))); + try { + IndexedFileSystem fs = new IndexedFileSystem(new File("data/fs/" + release), true); + try { + ItemDefinitionParser parser = new ItemDefinitionParser(fs); + ItemDefinition[] defs = parser.parse(); + ItemDefinition.init(defs); + + os.writeShort(defs.length); + for (int id = 0; id < defs.length; id++) { + ItemDefinition def = ItemDefinition.forId(id); + int type = getWeaponType(def); + os.writeByte(type); + if (type != -1) { + os.writeBoolean(isTwoHanded(def)); + os.writeBoolean(isFullBody(def)); + os.writeBoolean(isFullHat(def)); + os.writeBoolean(isFullMask(def)); + os.writeByte(getAttackRequirement(def)); + os.writeByte(getStrengthRequirement(def)); + os.writeByte(getDefenceRequirement(def)); + os.writeByte(getRangedRequirement(def)); + os.writeByte(getMagicRequirement(def)); + } + } + } finally { + fs.close(); + } + } finally { + os.close(); + } + } + + /** + * Checks if the item is two handed. + * @param def The item. + * @return {@code true} if so, {@code false} otherwise. + */ + private static boolean isTwoHanded(ItemDefinition def) { + int id = def.getId(); + String name = def.getName(); + if (name == null) + name = "null"; + if (id == 4212) + return true; + else if (id == 4214) + return true; + else if (name.endsWith("2h sword")) + return true; + else if (name.endsWith("longbow")) + return true; + else if (name.equals("Seercull")) + return true; + else if (name.endsWith("shortbow")) + return true; + else if (name.endsWith("Longbow")) + return true; + else if (name.endsWith("Shortbow")) + return true; + else if (name.endsWith("bow full")) + return true; + else if (name.endsWith("halberd")) + return true; + else if (name.equals("Granite maul")) + return true; + else if (name.equals("Karils crossbow")) + return true; + else if (name.equals("Torags hammers")) + return true; + else if (name.equals("Veracs flail")) + return true; + else if (name.equals("Dharoks greataxe")) + return true; + else if (name.equals("Guthans warspear")) + return true; + else if (name.equals("Tzhaar-ket-om")) + return true; + else if (name.endsWith("godsword")) + return true; + else if (name.equals("Saradomin sword")) + return true; + else + return false; + } + + /** + * Gets the ranged requirement. + * @param def The item. + * @return The required level. + */ + private static int getRangedRequirement(ItemDefinition def) { + int id = def.getId(); + String name = def.getName(); + if (name == null) + name = "null"; + if (id == 2499) + return 50; + if (id == 1135) + return 40; + if (id == 1099) + return 40; + if (id == 1065) + return 40; + if (id == 2501) + return 60; + if (id == 2503) + return 70; + if (id == 2487) + return 50; + if (id == 2489) + return 60; + if (id == 2495) + return 60; + if (id == 2491) + return 70; + if (id == 2493) + return 50; + if (id == 2505) + return 60; + if (id == 2507) + return 70; + if (id == 859) + return 40; + if (id == 861) + return 40; + if (id == 7370) + return 40; + if (id == 7372) + return 40; + if (id == 7378) + return 40; + if (id == 7380) + return 40; + if (id == 7374) + return 50; + if (id == 7376) + return 50; + if (id == 7382) + return 50; + if (id == 7384) + return 50; + if (name.equals("Coif")) + return 20; + if (name.startsWith("Studded chaps")) + return 20; + if (name.startsWith("Studded")) + return 20; + if (name.equals("Karils coif")) + return 70; + if (name.equals("Karils leathertop")) + return 70; + if (name.equals("Karils leatherskirt")) + return 70; + if (name.equals("Robin hood hat")) + return 40; + if (name.equals("Ranger boots")) + return 40; + if (name.equals("Crystal bow full")) + return 70; + if (name.equals("New crystal bow")) + return 70; + if (name.equals("Karils crossbow")) + return 70; + if (id == 2497) + return 70; + if (name.equals("Rune thrownaxe")) + return 40; + if (name.equals("Rune dart")) + return 40; + if (name.equals("Rune javelin")) + return 40; + if (name.equals("Rune knife")) + return 40; + if (name.equals("Adamant thrownaxe")) + return 30; + if (name.equals("Adamant dart")) + return 30; + if (name.equals("Adamant javelin")) + return 30; + if (name.equals("Adamant knife")) + return 30; + if (name.equals("Toktz-xil-ul")) + return 60; + if (name.equals("Seercull")) + return 50; + if (name.equals("Bolt rack")) + return 70; + if (name.equals("Rune arrow")) + return 40; + if (name.equals("Adamant arrow")) + return 30; + if (name.equals("Mithril arrow")) + return 1; + else + return 1; + } + + /** + * Gets the magic requirement. + * @param def The item. + * @return The required level. + */ + private static int getMagicRequirement(ItemDefinition def) { + String name = def.getName(); + if (name == null) + name = "null"; + if (name.equals("Mystic hat")) + return 40; + if (name.equals("Mystic robe top")) + return 40; + if (name.equals("Mystic robe bottom")) + return 40; + if (name.equals("Mystic gloves")) + return 40; + if (name.equals("Mystic boots")) + return 40; + if (name.equals("Slayer's staff")) + return 50; + if (name.equals("Enchanted hat")) + return 40; + if (name.equals("Enchanted top")) + return 40; + if (name.equals("Enchanted robe")) + return 40; + if (name.equals("Splitbark helm")) + return 40; + if (name.equals("Splitbark body")) + return 40; + if (name.equals("Splitbark gauntlets")) + return 40; + if (name.equals("Splitbark legs")) + return 40; + if (name.equals("Splitbark greaves")) + return 40; + if (name.equals("Infinity gloves")) + return 50; + if (name.equals("Infinity hat")) + return 50; + if (name.equals("Infinity top")) + return 50; + if (name.equals("Infinity bottoms")) + return 50; + if (name.equals("Infinity boots")) + return 50; + if (name.equals("Ahrims hood")) + return 70; + if (name.equals("Ahrims robetop")) + return 70; + if (name.equals("Ahrims robeskirt")) + return 70; + if (name.equals("Ahrims staff")) + return 70; + if (name.equals("Saradomin cape")) + return 60; + if (name.equals("Saradomin staff")) + return 60; + if (name.equals("Zamorak cape")) + return 60; + if (name.equals("Zamorak staff")) + return 60; + if (name.equals("Guthix cape")) + return 60; + if (name.equals("Guthix staff")) + return 60; + if (name.equals("mud staff")) + return 30; + if (name.equals("Fire battlestaff")) + return 30; + return 1; + } + + /** + * Gets the strength requirement. + * @param def The item. + * @return The required level. + */ + private static int getStrengthRequirement(ItemDefinition def) { + String name = def.getName(); + if (name == null) + name = "null"; + if (name.equals("Torags hammers")) + return 70; + if (name.equals("Dharoks greataxe")) + return 70; + if (name.equals("Granite maul")) + return 50; + if (name.equals("Granite legs")) + return 99; + if (name.equals("Tzhaar-ket-om")) + return 60; + if (name.equals("Granite shield")) + return 50; + return 1; + } + + /** + * Gets the attack requirement. + * @param def The item. + * @return The required level. + */ + private static int getAttackRequirement(ItemDefinition def) { + String name = def.getName(); + if (name == null) + name = "null"; + if (name.equals("Black sword")) + return 10; + if (name.equals("Black dagger")) + return 10; + if (name.equals("Black spear")) + return 10; + if (name.equals("Black longsword")) + return 10; + if (name.equals("Black scimitar")) + return 10; + if (name.equals("Black axe")) + return 10; + if (name.equals("Black battleaxe")) + return 10; + if (name.equals("Black mace")) + return 10; + if (name.equals("Black halberd")) + return 10; + if (name.equals("Mithril sword")) + return 20; + if (name.equals("Mithril dagger")) + return 20; + if (name.equals("Mithril spear")) + return 20; + if (name.equals("Mihril longsword")) + return 20; + if (name.equals("Mithril scimitar")) + return 20; + if (name.equals("Mithril axe")) + return 20; + if (name.equals("Mithril battleaxe")) + return 20; + if (name.equals("Mithril mace")) + return 20; + if (name.equals("Mithril halberd")) + return 20; + if (name.equals("Adamant sword")) + return 30; + if (name.equals("Adamant dagger")) + return 30; + if (name.equals("Adamant spear")) + return 30; + if (name.equals("Adamant longsword")) + return 30; + if (name.equals("Adamant scimitar")) + return 30; + if (name.equals("Adamant axe")) + return 30; + if (name.equals("Adamant battleaxe")) + return 30; + if (name.equals("Adamant mace")) + return 30; + if (name.equals("Adamant halberd")) + return 30; + if (name.equals("Rune sword")) + return 40; + if (name.equals("Rune dagger")) + return 40; + if (name.equals("Rune spear")) + return 40; + if (name.equals("Rune longsword")) + return 40; + if (name.equals("Rune scimitar")) + return 40; + if (name.equals("Rune axe")) + return 40; + if (name.equals("Rune battleaxe")) + return 40; + if (name.equals("Rune mace")) + return 40; + if (name.equals("Rune halberd")) + return 40; + if (name.equals("Dragon sword")) + return 60; + if (name.equals("Dragon dagger(s)")) + return 60; + if (name.equals("Dragon dagger")) + return 60; + if (name.startsWith("Dragon spear")) + return 60; + if (name.equals("Dragon longsword")) + return 60; + if (name.equals("Dragon scimitar")) + return 60; + if (name.equals("Dragon axe")) + return 60; + if (name.equals("Dragon battleaxe")) + return 60; + if (name.equals("Dragon mace")) + return 60; + if (name.equals("Dragon halberd")) + return 60; + if (name.equals("Abyssal whip")) + return 70; + if (name.equals("Veracs flail")) + return 70; + if (name.equals("Torags hammers")) + return 70; + if (name.equals("Dharoks greataxe")) + return 70; + if (name.equals("Guthans warspear")) + return 70; + if (name.equals("Ahrims staff")) + return 70; + if (name.equals("Granite maul")) + return 50; + if (name.equals("Toktz-xil-ak")) + return 60; + if (name.equals("Tzhaar-ket-em")) + return 60; + if (name.equals("Toktz-xil-ek")) + return 60; + if (name.equals("Granite legs")) + return 99; + if (name.equals("Mud staff")) + return 30; + if (name.equals("Armadyl godsword")) + return 75; + if (name.equals("Bandos godsword")) + return 75; + if (name.equals("Saradomin godsword")) + return 75; + if (name.equals("Zamorak godsword")) + return 75; + if (name.equals("Lava battlestaff")) + return 30; + if (name.equals("Toktz-mej-tal")) + return 60; + if (name.equals("Ancient staff")) + return 50; + return 1; + } + + /** + * Gets the defence requirement. + * @param def The item. + * @return The required level. + */ + private static int getDefenceRequirement(ItemDefinition def) { + int id = def.getId(); + String name = def.getName(); + if (name == null) + name = "null"; + if (name.equals("Rune boots")) + return 40; + if (id == 2499) + return 40; + if (id == 4123) + return 5; + if (id == 4125) + return 10; + if (id == 4127) + return 20; + if (id == 4129) + return 30; + if (id == 7990) + return 60; + if (id == 2501) + return 40; + if (id == 1131) + return 10; + if (id == 2503) + return 40; + if (id == 1135) + return 40; + if (id == 7462) + return 42; + if (id == 7461) + return 42; + if (id == 7460) + return 42; + if (id == 7459) + return 20; + if (id == 7458) + return 1; + if (id == 7457) + return 1; + if (id == 7456) + return 1; + if (name.equals("White med helm")) + return 10; + if (name.equals("White chainbody")) + return 10; + if (name.startsWith("White full helm")) + return 10; + if (name.startsWith("White platebody")) + return 10; + if (name.startsWith("White plateskirt")) + return 10; + if (name.startsWith("White platelegs")) + return 10; + if (name.startsWith("White kiteshield")) + return 10; + if (name.startsWith("White sq shield")) + return 10; + if (name.startsWith("Studded chaps")) + return 1; + if (name.startsWith("Studded")) + return 20; + if (name.startsWith("Black kiteshield(h)")) + return 10; + if (name.startsWith("Rune kiteshield(h)")) + return 40; + if (name.equals("Black med helm")) + return 10; + if (name.equals("Black chainbody")) + return 10; + if (name.startsWith("Black full helm")) + return 10; + if (name.startsWith("Black platebody")) + return 10; + if (name.startsWith("Black plateskirt")) + return 10; + if (name.startsWith("Black platelegs")) + return 10; + if (name.startsWith("Black kiteshield")) + return 10; + if (name.startsWith("Black sq shield")) + return 10; + if (name.equals("Mithril med helm")) + return 20; + if (name.equals("Mithril chainbody")) + return 20; + if (name.startsWith("Mithril full helm")) + return 20; + if (name.startsWith("Mithril platebody")) + return 20; + if (name.startsWith("Mithril plateskirt")) + return 20; + if (name.startsWith("Mithril platelegs")) + return 20; + if (name.startsWith("Mithril kiteshield")) + return 20; + if (name.startsWith("Mithril sq shield")) + return 20; + if (name.equals("Adamant med helm")) + return 30; + if (name.equals("Adamant chainbody")) + return 30; + if (name.startsWith("Adamant full helm")) + return 30; + if (name.startsWith("Adamant platebody")) + return 30; + if (name.startsWith("Adamant plateskirt")) + return 30; + if (name.startsWith("Adamant platelegs")) + return 30; + if (name.startsWith("Adamant kiteshield")) + return 30; + if (name.startsWith("Adamant sq shield")) + return 30; + if (name.startsWith("Adam full helm")) + return 30; + if (name.startsWith("Adam platebody")) + return 30; + if (name.startsWith("Adam plateskirt")) + return 30; + if (name.startsWith("Adam platelegs")) + return 30; + if (name.startsWith("Adam kiteshield")) + return 30; + if (name.startsWith("Adam kiteshield(h)")) + return 30; + if (name.startsWith("D-hide body(g)")) + return 40; + if (name.startsWith("D-hide body(t)")) + return 40; + if (name.equals("Dragon sq shield")) + return 60; + if (name.equals("Dragon med helm")) + return 60; + if (name.equals("Dragon chainbody")) + return 60; + if (name.equals("Dragon plateskirt")) + return 60; + if (name.equals("Dragon platelegs")) + return 60; + if (name.equals("Dragon sq shield")) + return 60; + if (name.equals("Rune med helm")) + return 40; + if (name.equals("Rune chainbody")) + return 40; + if (name.startsWith("Rune full helm")) + return 40; + if (name.startsWith("Rune platebody")) + return 40; + if (name.startsWith("Rune plateskirt")) + return 40; + if (name.startsWith("Rune platelegs")) + return 40; + if (name.startsWith("Rune kiteshield")) + return 40; + if (name.startsWith("Zamorak full helm")) + return 40; + if (name.startsWith("Zamorak platebody")) + return 40; + if (name.startsWith("Zamorak plateskirt")) + return 40; + if (name.startsWith("Zamorak platelegs")) + return 40; + if (name.startsWith("Zamorak kiteshield")) + return 40; + if (name.startsWith("Guthix full helm")) + return 40; + if (name.startsWith("Guthix platebody")) + return 40; + if (name.startsWith("Guthix plateskirt")) + return 40; + if (name.startsWith("Guthix platelegs")) + return 40; + if (name.startsWith("Guthix kiteshield")) + return 40; + if (name.startsWith("Saradomin full")) + return 40; + if (name.startsWith("Saradomrangedin plate")) + return 40; + if (name.startsWith("Saradomin plateskirt")) + return 40; + if (name.startsWith("Saradomin legs")) + return 40; + if (name.startsWith("Zamorak kiteshield")) + return 40; + if (name.startsWith("Rune sq shield")) + return 40; + if (name.equals("Gilded full helm")) + return 40; + if (name.equals("Gilded platebody")) + return 40; + if (name.equals("Gilded plateskirt")) + return 40; + if (name.equals("Gilded platelegs")) + return 40; + if (name.equals("Gilded kiteshield")) + return 40; + if (name.equals("Fighter torso")) + return 40; + if (name.equals("Granite legs")) + return 99; + if (name.equals("Toktz-ket-xil")) + return 60; + if (name.equals("Dharoks helm")) + return 70; + if (name.equals("Dharoks platebody")) + return 70; + if (name.equals("Dharoks platelegs")) + return 70; + if (name.equals("Guthans helm")) + return 70; + if (name.equals("Guthans platebody")) + return 70; + if (name.equals("Guthans chainskirt")) + return 70; + if (name.equals("Torags helm")) + return 70; + if (name.equals("Torags platebody")) + return 70; + if (name.equals("Torags platelegs")) + return 70; + if (name.equals("Veracs helm")) + return 70; + if (name.equals("Veracs brassard")) + return 70; + if (name.equals("Veracs plateskirt")) + return 70; + if (name.equals("Ahrims hood")) + return 70; + if (name.equals("Ahrims robetop")) + return 70; + if (name.equals("Ahrims robeskirt")) + return 70; + if (name.equals("Karils coif")) + return 70; + if (name.equals("Karils leathertop")) + return 70; + if (name.equals("Karils leatherskirt")) + return 70; + if (name.equals("Granite shield")) + return 50; + if (name.equals("New crystal shield")) + return 70; + if (name.equals("Archer helm")) + return 45; + if (name.equals("Berserker helm")) + return 45; + if (name.equals("Warrior helm")) + return 45; + if (name.equals("Farseer helm")) + return 45; + if (name.equals("Initiate helm")) + return 20; + if (name.equals("Initiate platemail")) + return 20; + if (name.equals("Initiate platelegs")) + return 20; + if (name.equals("Dragonhide body")) + return 40; + if (name.equals("Mystic hat")) + return 20; + if (name.equals("Mystic robe top")) + return 20; + if (name.equals("Mystic robe bottom")) + return 20; + if (name.equals("Mystic gloves")) + return 20; + if (name.equals("Mystic boots")) + return 20; + if (name.equals("Enchanted hat")) + return 20; + if (name.equals("Enchanted top")) + return 20; + if (name.equals("Enchanted robe")) + return 20; + if (name.equals("Splitbark helm")) + return 40; + if (name.equals("Splitbark body")) + return 40; + if (name.equals("Splitbark gauntlets")) + return 40; + if (name.equals("Splitbark legs")) + return 40; + if (name.equals("Splitbark greaves")) + return 40; + if (name.equals("Infinity gloves")) + return 25; + if (name.equals("Infinity hat")) + return 25; + if (name.equals("Infinity top")) + return 25; + if (name.equals("Infinity bottoms")) + return 25; + if (name.equals("Infinity boots")) + return 25; + return 1; + } + + /** + * Gets the weapon type. + * @param def The item. + * @return The weapon type, or {@code -1} if it is not a weapon. + */ + private static int getWeaponType(ItemDefinition def) { + String name = def.getName(); + if (name == null) + name = "null"; + for (int i = 0; i < EquipmentConstants.CAPES.length; i++) { + if (name.contains(EquipmentConstants.CAPES[i])) + return 1; + } + for (int i = 0; i < EquipmentConstants.HATS.length; i++) { + if (name.contains(EquipmentConstants.HATS[i])) + return 0; + } + for (int i = 0; i < EquipmentConstants.BOOTS.length; i++) { + if (name.endsWith(EquipmentConstants.BOOTS[i]) || name.startsWith(EquipmentConstants.BOOTS[i])) + return 10; + } + for (int i = 0; i < EquipmentConstants.GLOVES.length; i++) { + if (name.endsWith(EquipmentConstants.GLOVES[i]) || name.startsWith(EquipmentConstants.GLOVES[i])) + return 9; + } + for (int i = 0; i < EquipmentConstants.SHIELDS.length; i++) { + if (name.contains(EquipmentConstants.SHIELDS[i])) + return 5; + } + for (int i = 0; i < EquipmentConstants.AMULETS.length; i++) { + if (name.endsWith(EquipmentConstants.AMULETS[i]) || name.startsWith(EquipmentConstants.AMULETS[i])) + return 2; + } + for (int i = 0; i < EquipmentConstants.ARROWS.length; i++) { + if (name.endsWith(EquipmentConstants.ARROWS[i]) || name.startsWith(EquipmentConstants.ARROWS[i])) + return 13; + } + for (int i = 0; i < EquipmentConstants.RINGS.length; i++) { + if (name.endsWith(EquipmentConstants.RINGS[i]) || name.startsWith(EquipmentConstants.RINGS[i])) + return 12; + } + for (int i = 0; i < EquipmentConstants.BODY.length; i++) { + if (name.contains(EquipmentConstants.BODY[i])) + return 4; + } + for (int i = 0; i < EquipmentConstants.LEGS.length; i++) { + if (name.contains(EquipmentConstants.LEGS[i])) + return 7; + } + for (int i = 0; i < EquipmentConstants.WEAPONS.length; i++) { + if (name.endsWith(EquipmentConstants.WEAPONS[i]) || name.startsWith(EquipmentConstants.WEAPONS[i])) + return 3; + } + return -1; + } + + /** + * Checks if the item is a full body item. + * @param def The item. + * @return {@code true} if so, {@code false} otherwise. + */ + private static boolean isFullBody(ItemDefinition def) { + String name = def.getName(); + if (name == null) + name = "null"; + for (int i = 0; i < EquipmentConstants.FULL_BODIES.length; i++) { + if (name.contains(EquipmentConstants.FULL_BODIES[i])) { + return true; + } + } + return false; + } + + /** + * Checks if the item is a full hat item. + * @param def The item. + * @return {@code true} if so, {@code false} otherwise. + */ + private static boolean isFullHat(ItemDefinition def) { + String name = def.getName(); + if (name == null) + name = "null"; + for (int i = 0; i < EquipmentConstants.FULL_HATS.length; i++) { + if (name.endsWith(EquipmentConstants.FULL_HATS[i])) { + return true; + } + } + return false; + } + + /** + * Checks if the item is a full mask item. + * @param def The item. + * @return {@code true} if so, {@code false} otherwise. + */ + private static boolean isFullMask(ItemDefinition def) { + String name = def.getName(); + if (name == null) + name = "null"; + for (int i = 0; i < EquipmentConstants.FULL_MASKS.length; i++) { + if (name.endsWith(EquipmentConstants.FULL_MASKS[i])) { + return true; + } + } + return false; + } + + /** + * Default private constructor to prevent instantiation. + */ + private EquipmentUpdater() { + + } + +} diff --git a/src/org/apollo/tools/NoteUpdater.java b/src/org/apollo/tools/NoteUpdater.java new file mode 100644 index 000000000..4a2e01b47 --- /dev/null +++ b/src/org/apollo/tools/NoteUpdater.java @@ -0,0 +1,68 @@ +package org.apollo.tools; + +import java.io.BufferedOutputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.util.HashMap; +import java.util.Map; + +import org.apollo.fs.IndexedFileSystem; +import org.apollo.fs.parser.ItemDefinitionParser; +import org.apollo.game.model.def.ItemDefinition; + +/** + * A tool for updating the note data. + * @author Graham + */ +public final class NoteUpdater { + + /** + * The entry point of the application. + * @param args The command line arguments. + * @throws Exception if an error occurs. + */ + public static void main(String[] args) throws Exception { + if (args.length != 1) { + System.err.println("Usage:"); + System.err.println(" java -cp ... org.apollo.tools.NoteUpdater [release]"); + return; + } + String release = args[0]; + + DataOutputStream os = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("data/note-" + release + ".dat"))); + try { + IndexedFileSystem fs = new IndexedFileSystem(new File("data/fs/" + release), true); + try { + ItemDefinitionParser parser = new ItemDefinitionParser(fs); + ItemDefinition[] defs = parser.parse(); + ItemDefinition.init(defs); + + os.writeShort(defs.length); + + Map itemToNote = new HashMap(); + + for (int id = 0; id < defs.length; id++) { + ItemDefinition def = ItemDefinition.forId(id); + if (def.isNote()) { + itemToNote.put(def.getNoteInfoId(), def.getId()); + } + } + + for (int id = 0; id < defs.length; id++) { + if (itemToNote.containsKey(id)) { + os.writeBoolean(true); // notable + os.writeShort(itemToNote.get(id)); + } else { + os.writeBoolean(false); // not notable + } + } + } finally { + fs.close(); + } + } finally { + os.close(); + } + } + +} diff --git a/src/org/apollo/tools/package-info.java b/src/org/apollo/tools/package-info.java new file mode 100644 index 000000000..63be4cea5 --- /dev/null +++ b/src/org/apollo/tools/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains several stand-alone utilities. + */ +package org.apollo.tools; diff --git a/src/org/apollo/update/ChannelRequest.java b/src/org/apollo/update/ChannelRequest.java new file mode 100644 index 000000000..f53c34431 --- /dev/null +++ b/src/org/apollo/update/ChannelRequest.java @@ -0,0 +1,58 @@ +package org.apollo.update; + +import org.jboss.netty.channel.Channel; + +/** + * A specialised request which contains a channel as well as the request object + * itself. + * @author Graham + * @param The type of request. + */ +public final class ChannelRequest implements Comparable> { + + /** + * The channel. + */ + private final Channel channel; + + /** + * The request. + */ + private final T request; + + /** + * Creates a new channel request. + * @param channel The channel. + * @param request The request. + */ + public ChannelRequest(Channel channel, T request) { + this.channel = channel; + this.request = request; + } + + /** + * Gets the channel. + * @return The channel. + */ + public Channel getChannel() { + return channel; + } + + /** + * Gets the request. + * @return The request. + */ + public T getRequest() { + return request; + } + + @SuppressWarnings("unchecked") + @Override + public int compareTo(ChannelRequest o) { + if (request instanceof Comparable && o.request instanceof Comparable) { + return ((Comparable) request).compareTo(o.request); + } + return 0; + } + +} diff --git a/src/org/apollo/update/HttpRequestWorker.java b/src/org/apollo/update/HttpRequestWorker.java new file mode 100644 index 000000000..0a5bd851a --- /dev/null +++ b/src/org/apollo/update/HttpRequestWorker.java @@ -0,0 +1,143 @@ +package org.apollo.update; + +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.util.Date; + +import org.apollo.fs.IndexedFileSystem; +import org.apollo.update.resource.CombinedResourceProvider; +import org.apollo.update.resource.HypertextResourceProvider; +import org.apollo.update.resource.ResourceProvider; +import org.apollo.update.resource.VirtualResourceProvider; +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.buffer.ChannelBuffers; +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelFutureListener; +import org.jboss.netty.handler.codec.http.DefaultHttpResponse; +import org.jboss.netty.handler.codec.http.HttpRequest; +import org.jboss.netty.handler.codec.http.HttpResponse; +import org.jboss.netty.handler.codec.http.HttpResponseStatus; + +/** + * A worker which services HTTP requests. + * @author Graham + */ +public final class HttpRequestWorker extends RequestWorker { + + /** + * The value of the server header. + */ + private static final String SERVER_IDENTIFIER = "JAGeX/3.1"; + + /** + * The directory with web files. + */ + private static final File WWW_DIRECTORY = new File("./data/www/"); + + /** + * The default character set. + */ + private static final Charset CHARACTER_SET = Charset.forName("ISO-8859-1"); + + /** + * Creates the HTTP request worker. + * @param dispatcher The dispatcher. + * @param fs The file system. + */ + public HttpRequestWorker(UpdateDispatcher dispatcher, IndexedFileSystem fs) { + super(dispatcher, new CombinedResourceProvider(new VirtualResourceProvider(fs), new HypertextResourceProvider(WWW_DIRECTORY))); + } + + @Override + protected ChannelRequest nextRequest(UpdateDispatcher dispatcher) throws InterruptedException { + return dispatcher.nextHttpRequest(); + } + + @Override + protected void service(ResourceProvider provider, Channel channel, HttpRequest request) throws IOException { + String path = request.getUri(); + ByteBuffer buf = provider.get(path); + + ChannelBuffer wrappedBuf; + HttpResponseStatus status = HttpResponseStatus.OK; + + String mimeType = getMimeType(request.getUri()); + + if (buf == null) { + status = HttpResponseStatus.NOT_FOUND; + wrappedBuf = createErrorPage(status, "The page you requested could not be found."); + mimeType = "text/html"; + } else { + wrappedBuf = ChannelBuffers.wrappedBuffer(buf); + } + + HttpResponse resp = new DefaultHttpResponse(request.getProtocolVersion(), status); + + resp.setHeader("Date", new Date()); + resp.setHeader("Server", SERVER_IDENTIFIER); + resp.setHeader("Content-type", mimeType + ", charset=" + CHARACTER_SET.name()); + resp.setHeader("Cache-control", "no-cache"); + resp.setHeader("Pragma", "no-cache"); + resp.setHeader("Expires", new Date(0)); + resp.setHeader("Connection", "close"); + resp.setHeader("Content-length", wrappedBuf.readableBytes()); + resp.setChunked(false); + resp.setContent(wrappedBuf); + + channel.write(resp).addListener(ChannelFutureListener.CLOSE); + } + + /** + * Gets the MIME type of a file by its name. + * @param name The file name. + * @return The MIME type. + */ + private String getMimeType(String name) { + if (name.endsWith("/")) { + name = name.concat("index.html"); + } + if (name.endsWith(".htm") || name.endsWith(".html")) { + return "text/html"; + } else if (name.endsWith(".css")) { + return "text/css"; + } else if (name.endsWith(".js")) { + return "text/javascript"; + } else if (name.endsWith(".jpg") || name.endsWith(".jpeg")) { + return "image/jpeg"; + } else if (name.endsWith(".gif")) { + return "image/gif"; + } else if (name.endsWith(".png")) { + return "image/png"; + } else if (name.endsWith(".txt")) { + return "text/plain"; + } + return "application/octect-stream"; + } + + /** + * Creates an error page. + * @param status The HTTP status. + * @param description The error description. + * @return The error page as a buffer. + */ + private ChannelBuffer createErrorPage(HttpResponseStatus status, String description) { + String title = status.getCode() + " " + status.getReasonPhrase(); + + StringBuilder bldr = new StringBuilder(); + + bldr.append(""); + bldr.append(title); + bldr.append("

"); + bldr.append(title); + bldr.append("

"); + bldr.append(description); + bldr.append("


"); + bldr.append(SERVER_IDENTIFIER); + bldr.append(" Server
"); + + return ChannelBuffers.copiedBuffer(bldr.toString(), Charset.defaultCharset()); + } + +} diff --git a/src/org/apollo/update/JagGrabRequestWorker.java b/src/org/apollo/update/JagGrabRequestWorker.java new file mode 100644 index 000000000..016846e44 --- /dev/null +++ b/src/org/apollo/update/JagGrabRequestWorker.java @@ -0,0 +1,47 @@ +package org.apollo.update; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import org.apollo.fs.IndexedFileSystem; +import org.apollo.net.codec.jaggrab.JagGrabRequest; +import org.apollo.net.codec.jaggrab.JagGrabResponse; +import org.apollo.update.resource.ResourceProvider; +import org.apollo.update.resource.VirtualResourceProvider; +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.buffer.ChannelBuffers; +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelFutureListener; + +/** + * A worker which services JAGGRAB requests. + * @author Graham + */ +public final class JagGrabRequestWorker extends RequestWorker { + + /** + * Creates the JAGGRAB request worker. + * @param dispatcher The dispatcher. + * @param fs The file system. + */ + public JagGrabRequestWorker(UpdateDispatcher dispatcher, IndexedFileSystem fs) { + super(dispatcher, new VirtualResourceProvider(fs)); + } + + @Override + protected ChannelRequest nextRequest(UpdateDispatcher dispatcher) throws InterruptedException { + return dispatcher.nextJagGrabRequest(); + } + + @Override + protected void service(ResourceProvider provider, Channel channel, JagGrabRequest request) throws IOException { + ByteBuffer buf = provider.get(request.getFilePath()); + if (buf == null) { + channel.close(); + } else { + ChannelBuffer wrapped = ChannelBuffers.wrappedBuffer(buf); + channel.write(new JagGrabResponse(wrapped)).addListener(ChannelFutureListener.CLOSE); + } + } + +} diff --git a/src/org/apollo/update/OnDemandRequestWorker.java b/src/org/apollo/update/OnDemandRequestWorker.java new file mode 100644 index 000000000..e3ed050c4 --- /dev/null +++ b/src/org/apollo/update/OnDemandRequestWorker.java @@ -0,0 +1,61 @@ +package org.apollo.update; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import org.apollo.fs.FileDescriptor; +import org.apollo.fs.IndexedFileSystem; +import org.apollo.net.codec.update.OnDemandRequest; +import org.apollo.net.codec.update.OnDemandResponse; +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.buffer.ChannelBuffers; +import org.jboss.netty.channel.Channel; + +/** + * A worker which services 'on-demand' requests. + * @author Graham + */ +public final class OnDemandRequestWorker extends RequestWorker { + + /** + * The maximum length of a chunk, in bytes. + */ + private static final int CHUNK_LENGTH = 500; + + /** + * Creates the 'on-demand' request worker. + * @param dispatcher The dispatcher. + * @param fs The file system. + */ + public OnDemandRequestWorker(UpdateDispatcher dispatcher, IndexedFileSystem fs) { + super(dispatcher, fs); + } + + @Override + protected ChannelRequest nextRequest(UpdateDispatcher dispatcher) throws InterruptedException { + return dispatcher.nextOnDemandRequest(); + } + + @Override + protected void service(IndexedFileSystem fs, Channel channel, OnDemandRequest request) throws IOException { + FileDescriptor desc = request.getFileDescriptor(); + + ByteBuffer buf = fs.getFile(desc); + int length = buf.remaining(); + + for (int chunk = 0; buf.remaining() > 0; chunk++) { + int chunkSize = buf.remaining(); + if (chunkSize > CHUNK_LENGTH) { + chunkSize = CHUNK_LENGTH; + } + + byte[] tmp = new byte[chunkSize]; + buf.get(tmp, 0, tmp.length); + ChannelBuffer chunkData = ChannelBuffers.wrappedBuffer(tmp, 0, chunkSize); + + OnDemandResponse response = new OnDemandResponse(desc, length, chunk, chunkData); + channel.write(response); + } + } + +} diff --git a/src/org/apollo/update/RequestWorker.java b/src/org/apollo/update/RequestWorker.java new file mode 100644 index 000000000..d6f4e3688 --- /dev/null +++ b/src/org/apollo/update/RequestWorker.java @@ -0,0 +1,93 @@ +package org.apollo.update; + +import java.io.IOException; + +import org.jboss.netty.channel.Channel; + +/** + * The base class for request workers. + * @author Graham + * @param The type of request. + * @param

The type of provider. + */ +public abstract class RequestWorker implements Runnable { + + /** + * The resource provider. + */ + private final P provider; + + /** + * The update dispatcher. + */ + private final UpdateDispatcher dispatcher; + + /** + * A flag indicating if the worker should be running. + */ + private boolean running = true; + + /** + * Creates the request worker with the specified file system. + * @param dispatcher The update dispatcher. + * @param provider The resource provider. + */ + public RequestWorker(UpdateDispatcher dispatcher, P provider) { + this.provider = provider; + this.dispatcher = dispatcher; + } + + /** + * Stops this worker. The worker's thread may need to be interrupted. + */ + public final void stop() { + synchronized (this) { + running = false; + } + } + + @Override + public final void run() { + while (true) { + synchronized (this) { + if (!running) { + break; + } + } + + ChannelRequest request; + try { + request = nextRequest(dispatcher); + } catch (InterruptedException e) { + continue; + } + + Channel channel = request.getChannel(); + + try { + service(provider, channel, request.getRequest()); + } catch (IOException e) { + e.printStackTrace(); + channel.close(); + } + } + } + + /** + * Gets the next request. + * @param dispatcher The dispatcher. + * @return The next request. + * @throws InterruptedException if the thread is interrupted. + */ + protected abstract ChannelRequest nextRequest(UpdateDispatcher dispatcher) throws InterruptedException; + + /** + * Services a request. + * @param provider The resource provider. + * @param channel The channel. + * @param request The request to service. + * @throws IOException if an I/O error occurs. + */ + protected abstract void service(P provider, Channel channel, T request) throws IOException; + +} diff --git a/src/org/apollo/update/UpdateConstants.java b/src/org/apollo/update/UpdateConstants.java new file mode 100644 index 000000000..c4c7bc34f --- /dev/null +++ b/src/org/apollo/update/UpdateConstants.java @@ -0,0 +1,16 @@ +package org.apollo.update; + +/** + * Holds update-related constants. + * @author Graham + */ +public final class UpdateConstants { + + /** + * Default private constructor to prevent instantiation by other classes. + */ + private UpdateConstants() { + + } + +} diff --git a/src/org/apollo/update/UpdateDispatcher.java b/src/org/apollo/update/UpdateDispatcher.java new file mode 100644 index 000000000..22ad7baf4 --- /dev/null +++ b/src/org/apollo/update/UpdateDispatcher.java @@ -0,0 +1,104 @@ +package org.apollo.update; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.PriorityBlockingQueue; + +import org.apollo.net.codec.jaggrab.JagGrabRequest; +import org.apollo.net.codec.update.OnDemandRequest; +import org.jboss.netty.channel.Channel; +import org.jboss.netty.handler.codec.http.HttpRequest; + +/** + * A class which dispatches requests to worker threads. + * @author Graham + */ +public final class UpdateDispatcher { + + /** + * The maximum size of a queue before requests are rejected. + */ + private static final int MAXIMUM_QUEUE_SIZE = 1024; + + /** + * A queue for pending 'on-demand' requests. + */ + private final BlockingQueue> onDemandQueue = new PriorityBlockingQueue>(); + + /** + * A queue for pending JAGGRAB requests. + */ + private final BlockingQueue> jagGrabQueue = new LinkedBlockingQueue>(); + + /** + * A queue for pending HTTP requests. + */ + private final BlockingQueue> httpQueue = new LinkedBlockingQueue>(); + + /** + * Gets the next 'on-demand' request from the queue, blocking if none are + * available. + * @return The 'on-demand' request. + * @throws InterruptedException if the thread is interrupted. + */ + ChannelRequest nextOnDemandRequest() throws InterruptedException { + return onDemandQueue.take(); + } + + /** + * Gets the next JAGGRAB request from the queue, blocking if none are + * available. + * @return The JAGGRAB request. + * @throws InterruptedException if the thread is interrupted. + */ + ChannelRequest nextJagGrabRequest() throws InterruptedException { + return jagGrabQueue.take(); + } + + /** + * Gets the next HTTP request from the queue, blocking if none are + * available. + * @return The HTTP request. + * @throws InterruptedException if the thread is interrupted. + */ + ChannelRequest nextHttpRequest() throws InterruptedException { + return httpQueue.take(); + } + + /** + * Dispatches an 'on-demand' request. + * @param channel The channel. + * @param request The request. + */ + public void dispatch(Channel channel, OnDemandRequest request) { + if (onDemandQueue.size() >= MAXIMUM_QUEUE_SIZE) { + channel.close(); + } + onDemandQueue.add(new ChannelRequest(channel, request)); + } + + /** + * Dispatches a JAGGRAB request. + * @param channel The channel. + * @param request The request. + */ + public void dispatch(Channel channel, JagGrabRequest request) { + if (jagGrabQueue.size() >= MAXIMUM_QUEUE_SIZE) { + channel.close(); + } + jagGrabQueue.add(new ChannelRequest(channel, request)); + } + + /** + * Dispatches a HTTP request. + * @param channel The channel. + * @param request The request. + */ + public void dispatch(Channel channel, HttpRequest request) { + if (httpQueue.size() >= MAXIMUM_QUEUE_SIZE) { + channel.close(); + } + httpQueue.add(new ChannelRequest(channel, request)); + } + +} diff --git a/src/org/apollo/update/UpdateService.java b/src/org/apollo/update/UpdateService.java new file mode 100644 index 000000000..56eb3c506 --- /dev/null +++ b/src/org/apollo/update/UpdateService.java @@ -0,0 +1,92 @@ +package org.apollo.update; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import org.apollo.Service; +import org.apollo.fs.IndexedFileSystem; + +/** + * A class which services file requests. + * @author Graham + */ +public final class UpdateService extends Service { + + /** + * The number of threads per request type. + */ + private static final int THREADS_PER_REQUEST_TYPE = Runtime.getRuntime().availableProcessors(); + + /** + * The number of request types. + */ + private static final int REQUEST_TYPES = 3; + + /** + * The executor service. + */ + private final ExecutorService service; + + /** + * A list of request workers. + */ + private final List> workers = new ArrayList>(); + + /** + * The update dispatcher. + */ + private final UpdateDispatcher dispatcher = new UpdateDispatcher(); + + /** + * Creates the update service. + */ + public UpdateService() { + int totalThreads = REQUEST_TYPES * THREADS_PER_REQUEST_TYPE; + service = Executors.newFixedThreadPool(totalThreads); + } + + /** + * Gets the update dispatcher. + * @return The update dispatcher. + */ + public UpdateDispatcher getDispatcher() { + return dispatcher; + } + + /** + * Starts the threads in the pool. + */ + @Override + public void start() { + int release = getContext().getRelease().getReleaseNumber(); + try { + File base = new File("./data/fs/" + release + "/"); + for (int i = 0; i < THREADS_PER_REQUEST_TYPE; i++) { + workers.add(new JagGrabRequestWorker(dispatcher, new IndexedFileSystem(base, true))); + workers.add(new OnDemandRequestWorker(dispatcher, new IndexedFileSystem(base, true))); + workers.add(new HttpRequestWorker(dispatcher, new IndexedFileSystem(base, true))); + } + + for (RequestWorker worker : workers) { + service.submit(worker); + } + } catch (Exception ex) { + throw new RuntimeException(ex); // TODO neater/more elegant way + } + } + + /** + * Stops the threads in the pool. + */ + public void stop() { + for (RequestWorker worker : workers) { + worker.stop(); + } + + service.shutdownNow(); + } + +} diff --git a/src/org/apollo/update/package-info.java b/src/org/apollo/update/package-info.java new file mode 100644 index 000000000..f87d93f9f --- /dev/null +++ b/src/org/apollo/update/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains classes related to the update server. + */ +package org.apollo.update; diff --git a/src/org/apollo/update/resource/CombinedResourceProvider.java b/src/org/apollo/update/resource/CombinedResourceProvider.java new file mode 100644 index 000000000..8dea9ecc4 --- /dev/null +++ b/src/org/apollo/update/resource/CombinedResourceProvider.java @@ -0,0 +1,40 @@ +package org.apollo.update.resource; + +import java.io.IOException; +import java.nio.ByteBuffer; + +/** + * A resource provider composed of multiple resource providers. + * @author Graham + */ +public final class CombinedResourceProvider extends ResourceProvider { + + /** + * An array of resource providers. + */ + private final ResourceProvider[] providers; + + /** + * Creates the combined resource providers. + * @param providers The providers this provider delegates to. + */ + public CombinedResourceProvider(ResourceProvider... providers) { + this.providers = providers; + } + + @Override + public boolean accept(String path) throws IOException { + return true; + } + + @Override + public ByteBuffer get(String path) throws IOException { + for (ResourceProvider provider : providers) { + if (provider.accept(path)) { + return provider.get(path); + } + } + return null; + } + +} diff --git a/src/org/apollo/update/resource/HypertextResourceProvider.java b/src/org/apollo/update/resource/HypertextResourceProvider.java new file mode 100644 index 000000000..39757d897 --- /dev/null +++ b/src/org/apollo/update/resource/HypertextResourceProvider.java @@ -0,0 +1,64 @@ +package org.apollo.update.resource; + +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.net.URI; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel.MapMode; + +/** + * A {@link ResourceProvider} which provides additional hypertext resources. + * @author Graham + */ +public final class HypertextResourceProvider extends ResourceProvider { + + /** + * The base directory from which documents are served. + */ + private final File base; + + /** + * Creates a new hypertext resource provider with the specified base + * directory. + * @param base The base directory. + */ + public HypertextResourceProvider(File base) { + this.base = base; + } + + @Override + public boolean accept(String path) throws IOException { + File f = new File(base, path); + URI target = f.toURI().normalize(); + if (target.toASCIIString().startsWith(base.toURI().normalize().toASCIIString())) { + if (f.isDirectory()) { + f = new File(f, "index.html"); + } + return f.exists(); + } + return false; + } + + @Override + public ByteBuffer get(String path) throws IOException { + File f = new File(base, path); + if (f.isDirectory()) { + f = new File(f, "index.html"); + } + if (!f.exists()) { + return null; + } + + RandomAccessFile raf = new RandomAccessFile(f, "r"); + ByteBuffer buf; + try { + buf = raf.getChannel().map(MapMode.READ_ONLY, 0, raf.length()); + } finally { + raf.close(); + } + + return buf; + } + +} diff --git a/src/org/apollo/update/resource/ResourceProvider.java b/src/org/apollo/update/resource/ResourceProvider.java new file mode 100644 index 000000000..ea62fbfcc --- /dev/null +++ b/src/org/apollo/update/resource/ResourceProvider.java @@ -0,0 +1,30 @@ +package org.apollo.update.resource; + +import java.io.IOException; +import java.nio.ByteBuffer; + +/** + * A class which provides resources. + * @author Graham + */ +public abstract class ResourceProvider { + + /** + * Checks that this provider can fulfil a request to the specified + * resource. + * @param path The path to the resource, e.g. {@code /crc}. + * @return {@code true} if the provider can fulfil a request to the + * resource, {@code false} otherwise. + * @throws IOException if an I/O error occurs. + */ + public abstract boolean accept(String path) throws IOException; + + /** + * Gets a resource by its path. + * @param path The path. + * @return The resource, or {@code null} if it doesn't exist. + * @throws IOException if an I/O error occurs. + */ + public abstract ByteBuffer get(String path) throws IOException; + +} diff --git a/src/org/apollo/update/resource/VirtualResourceProvider.java b/src/org/apollo/update/resource/VirtualResourceProvider.java new file mode 100644 index 000000000..e1edc80bc --- /dev/null +++ b/src/org/apollo/update/resource/VirtualResourceProvider.java @@ -0,0 +1,70 @@ +package org.apollo.update.resource; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import org.apollo.fs.IndexedFileSystem; + +/** + * A {@link ResourceProvider} which maps virtual resources (such as + * {@code /media}) to files in an {@link IndexedFileSystem}. + * @author Graham + */ +public final class VirtualResourceProvider extends ResourceProvider { + + /** + * An array of valid prefixes. + */ + private static final String[] VALID_PREFIXES = { + "crc", "title", "config", "interface", "media", "versionlist", + "textures", "wordenc", "sounds" + }; + + /** + * The file system. + */ + private final IndexedFileSystem fs; + + /** + * Creates a new virtual resource provider with the specified file system. + * @param fs The file system. + */ + public VirtualResourceProvider(IndexedFileSystem fs) { + this.fs = fs; + } + + @Override + public boolean accept(String path) throws IOException { + for (String prefix : VALID_PREFIXES) { + if (path.startsWith("/" + prefix)) { + return true; + } + } + return false; + } + + @Override + public ByteBuffer get(String path) throws IOException { + if (path.startsWith("/crc")) { + return fs.getCrcTable(); + } else if (path.startsWith("/title")) { + return fs.getFile(0, 1); + } else if (path.startsWith("/config")) { + return fs.getFile(0, 2); + } else if (path.startsWith("/interface")) { + return fs.getFile(0, 3); + } else if (path.startsWith("/media")) { + return fs.getFile(0, 4); + } else if (path.startsWith("/versionlist")) { + return fs.getFile(0, 5); + } else if (path.startsWith("/textures")) { + return fs.getFile(0, 6); + } else if (path.startsWith("/wordenc")) { + return fs.getFile(0, 7); + } else if (path.startsWith("/sounds")) { + return fs.getFile(0, 8); + } + return null; + } + +} diff --git a/src/org/apollo/update/resource/package-info.java b/src/org/apollo/update/resource/package-info.java new file mode 100644 index 000000000..3f2bf15ff --- /dev/null +++ b/src/org/apollo/update/resource/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains resource providers for the update server. + */ +package org.apollo.update.resource; diff --git a/src/org/apollo/util/ByteBufferUtil.java b/src/org/apollo/util/ByteBufferUtil.java new file mode 100644 index 000000000..14c4b95cc --- /dev/null +++ b/src/org/apollo/util/ByteBufferUtil.java @@ -0,0 +1,43 @@ +package org.apollo.util; + +import java.nio.ByteBuffer; + +import org.apollo.net.NetworkConstants; + +/** + * A utility class which contains {@link ByteBuffer}-related methods. + * @author Graham + */ +public final class ByteBufferUtil { + + /** + * 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); + } + + /** + * Reads a string from the specified buffer. + * @param buffer The buffer. + * @return The string. + */ + public static String readString(ByteBuffer buffer) { + StringBuilder bldr = new StringBuilder(); + char c; + while ((c = (char) buffer.get()) != NetworkConstants.STRING_TERMINATOR) { + bldr.append(c); + } + return bldr.toString(); + } + + /** + * Default private constructor to prevent instantiation. + */ + private ByteBufferUtil() { + + } + +} diff --git a/src/org/apollo/util/ChannelBufferUtil.java b/src/org/apollo/util/ChannelBufferUtil.java new file mode 100644 index 000000000..c39379a4d --- /dev/null +++ b/src/org/apollo/util/ChannelBufferUtil.java @@ -0,0 +1,34 @@ +package org.apollo.util; + +import org.apollo.net.NetworkConstants; +import org.jboss.netty.buffer.ChannelBuffer; + +/** + * A utility class which provides extra {@link ChannelBuffer}-related methods + * which deal with data types used in the protocol. + * @author Graham + */ +public final class ChannelBufferUtil { + + /** + * Reads a string from the specified buffer. + * @param buffer The buffer. + * @return The string. + */ + public static String readString(ChannelBuffer buffer) { + StringBuilder builder = new StringBuilder(); + int character; + while (buffer.readable() && ((character = buffer.readUnsignedByte()) != NetworkConstants.STRING_TERMINATOR)) { + builder.append((char) character); + } + return builder.toString(); + } + + /** + * Default private constructor to prevent instantiation by other classes. + */ + private ChannelBufferUtil() { + + } + +} diff --git a/src/org/apollo/util/CharacterRepository.java b/src/org/apollo/util/CharacterRepository.java new file mode 100644 index 000000000..cb3a4dfb9 --- /dev/null +++ b/src/org/apollo/util/CharacterRepository.java @@ -0,0 +1,180 @@ +package org.apollo.util; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +import org.apollo.game.model.Character; + +/** + * A {@link CharacterRepository} is a repository of {@link Character}s that are + * currently active in the game world. + * @author Graham + * @param The type of character. + */ +public final class CharacterRepository implements Iterable { + + /** + * The array of characters in this repository. + */ + private final Character[] characters; + + /** + * The current size of this repository. + */ + private int size = 0; + + /** + * The position of the next free index. + */ + private int pointer = 0; + + /** + * Creates a new character repository with the specified capacity. + * @param capacity The maximum number of characters that can be present in + * the repository. + */ + public CharacterRepository(int capacity) { + this.characters = new Character[capacity]; + } + + /** + * Gets the size of this repository. + * @return The number of characters in this repository. + */ + public int size() { + return size; + } + + /** + * Gets the capacity of this repository. + * @return The maximum size of this repository. + */ + public int capacity() { + return characters.length; + } + + /** + * Adds a character to the repository. + * @param character The character to add. + * @return {@code true} if the character was added, {@code false} if the + * size has reached the capacity of this repository. + */ + public boolean add(T character) { + if (size == characters.length) { + return false; + } + int index = -1; + for (int i = pointer; i < characters.length; i++) { + if (characters[i] == null) { + index = i; + break; + } + } + if (index == -1) { + for (int i = 0; i < pointer; i++) { + if (characters[i] == null) { + index = i; + break; + } + } + } + if (index == -1) { + return false; // shouldn't happen, but just in case + } + characters[index] = character; + character.setIndex(index + 1); + if (index == (characters.length - 1)) { + pointer = 0; + } else { + pointer = index; + } + size++; + return true; + } + + /** + * Removes a character from the repository. + * @param character The character to remove. + * @return {@code true} if the character was removed, {@code false} if it + * was not (e.g. if it was never added or has been removed already). + */ + public boolean remove(T character) { + int index = character.getIndex() - 1; + if (index < 0 || index >= characters.length) { + return false; + } + if (characters[index] == character) { + characters[index] = null; + character.setIndex(-1); + size--; + return true; + } else { + return false; + } + } + + @Override + public Iterator iterator() { + return new CharacterRepositoryIterator(); + } + + /** + * The {@link Iterator} implementation for the {@link CharacterRepository} + * class. + * @author Graham + */ + private final class CharacterRepositoryIterator implements Iterator { + + /** + * The previous index of this iterator. + */ + private int previousIndex = -1; + + /** + * The current index of this iterator. + */ + private int index = 0; + + @Override + public boolean hasNext() { + for (int i = index; i < characters.length; i++) { + if (characters[i] != null) { + index = i; + return true; + } + } + return false; + } + + @SuppressWarnings("unchecked") + @Override + public T next() { + T character = null; + for (int i = index; i < characters.length; i++) { + if (characters[i] != null) { + character = (T) characters[i]; + index = i; + break; + } + } + if (character == null) { + throw new NoSuchElementException(); + } + previousIndex = index; + index++; + return character; + } + + @SuppressWarnings("unchecked") + @Override + public void remove() { + if (previousIndex == -1) { + throw new IllegalStateException(); + } + CharacterRepository.this.remove((T) characters[previousIndex]); + previousIndex = -1; + } + + } + +} diff --git a/src/org/apollo/util/CompressionUtil.java b/src/org/apollo/util/CompressionUtil.java new file mode 100644 index 000000000..55726740b --- /dev/null +++ b/src/org/apollo/util/CompressionUtil.java @@ -0,0 +1,103 @@ +package org.apollo.util; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.util.zip.DeflaterOutputStream; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; + +import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream; +import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream; + +/** + * A utility class for performing compression/uncompression. + * @author Graham + */ +public final class CompressionUtil { + + /** + * Ungzips the compressed array and places the results into the uncompressed array. + * @param compressed The compressed array. + * @param uncompressed The uncompressed array. + * @throws IOException if an I/O error occurs. + */ + public static void ungzip(byte[] compressed, byte[] uncompressed) throws IOException { + DataInputStream is = new DataInputStream(new GZIPInputStream(new ByteArrayInputStream(compressed))); + try { + is.readFully(uncompressed); + } finally { + is.close(); + } + } + + /** + * Unbzip2s the compressed array and places the result into the uncompressed array. + * @param compressed The compressed array. + * @param uncompressed The uncompressed array. + * @throws IOException if an I/O error occurs. + */ + public static void unbzip2(byte[] compressed, byte[] uncompressed) throws IOException { + byte[] newCompressed = new byte[compressed.length + 4]; + newCompressed[0] = 'B'; + newCompressed[1] = 'Z'; + newCompressed[2] = 'h'; + newCompressed[3] = '1'; + System.arraycopy(compressed, 0, newCompressed, 4, compressed.length); + + DataInputStream is = new DataInputStream(new BZip2CompressorInputStream(new ByteArrayInputStream(newCompressed))); + try { + is.readFully(uncompressed); + } finally { + is.close(); + } + } + + /** + * Gzips the specified array. + * @param bytes The uncompressed array. + * @return The compressed array. + * @throws IOException if an I/O error occurs. + */ + public static byte[] gzip(byte[] bytes) throws IOException { + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + DeflaterOutputStream os = new GZIPOutputStream(bout); + try { + os.write(bytes); + os.finish(); + return bout.toByteArray(); + } finally { + os.close(); + } + } + + /** + * Bzip2s the specified array. + * @param bytes The uncompressed array. + * @return The compressed array. + * @throws IOException if an I/O error occurs. + */ + public static byte[] bzip2(byte[] bytes) throws IOException { + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + BZip2CompressorOutputStream os = new BZip2CompressorOutputStream(bout, 1); + try { + os.write(bytes); + os.finish(); + byte[] compressed = bout.toByteArray(); + byte[] newCompressed = new byte[compressed.length - 4]; + System.arraycopy(compressed, 4, newCompressed, 0, newCompressed.length); + return newCompressed; + } finally { + os.close(); + } + } + + /** + * Default private constructor to prevent instantiation. + */ + private CompressionUtil() { + + } + +} diff --git a/src/org/apollo/util/EnumerationUtil.java b/src/org/apollo/util/EnumerationUtil.java new file mode 100644 index 000000000..5cdd4c79f --- /dev/null +++ b/src/org/apollo/util/EnumerationUtil.java @@ -0,0 +1,48 @@ +package org.apollo.util; + +import java.util.Enumeration; +import java.util.Iterator; + +/** + * A utility class for wrapping old {@link Enumeration} objects inside an + * {@link Iterator} to allow for greater compatibility. + * @author Graham + */ +public final class EnumerationUtil { + + /** + * Returns an {@link Iterator} which wraps around the specified + * {@link Enumeration}. + * @param The type of object that is iterated over. + * @param enumeration The {@link Enumeration}. + * @return An {@link Iterator}. + */ + public static Iterator asIterator(final Enumeration enumeration) { + return new Iterator() { + + @Override + public boolean hasNext() { + return enumeration.hasMoreElements(); + } + + @Override + public E next() { + return enumeration.nextElement(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + }; + } + + /** + * Default private constructor to prevent instantiation by other classes. + */ + private EnumerationUtil() { + + } + +} diff --git a/src/org/apollo/util/LanguageUtil.java b/src/org/apollo/util/LanguageUtil.java new file mode 100644 index 000000000..f96999d97 --- /dev/null +++ b/src/org/apollo/util/LanguageUtil.java @@ -0,0 +1,27 @@ +package org.apollo.util; + +/** + * A utility class which contains language-related methods. + * @author Graham + */ +public final class LanguageUtil { + + /** + * Gets the indefinite article of a 'thing'. + * @param thing The thing. + * @return The indefinite article. + */ + public static String getIndefiniteArticle(String thing) { + char first = thing.toLowerCase().charAt(0); + boolean vowel = first == 'a' || first == 'e' || first == 'i' || first == 'o' || first == 'u'; + return vowel ? "an" : "a"; + } + + /** + * Default private constructor to prevent instantiation. + */ + private LanguageUtil() { + + } + +} diff --git a/src/org/apollo/util/NameUtil.java b/src/org/apollo/util/NameUtil.java new file mode 100644 index 000000000..2aa79c26d --- /dev/null +++ b/src/org/apollo/util/NameUtil.java @@ -0,0 +1,68 @@ +package org.apollo.util; + +/** + * A class which contains name-related utility methods. + * @author Graham + */ +public final class NameUtil { + + /** + * An array of valid characters in a player name encoded as a long. + */ + private static final char[] NAME_CHARS = { + '_', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', + 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', + 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '!', '@', + '#', '$', '%', '^', '&', '*', '(', ')', '-', '+', '=', ':', ';', + '.', '>', '<', ',', '"', '[', ']', '|', '?', '/', '`' + }; + + /** + * Converts a player name to a long. + * @param name The player name. + * @return The long. + */ + public static long encodeBase37(String name) { + if (name.length() > 12) { + throw new IllegalArgumentException("name too long"); + } + long l = 0L; + for (int i = 0; i < name.length(); i++) { + char c = name.charAt(i); + l *= 37L; + if (c >= 'A' && c <= 'Z') { + l += (1 + c) - 65; + } else if (c >= 'a' && c <= 'z') { + l += (1 + c) - 97; + } else if (c >= '0' && c <= '9') { + l += (27 + c) - 48; + } + } + for (; l % 37L == 0L && l != 0L; l /= 37L); + return l; + } + + /** + * Converts a long to a player name. + * @param l The long. + * @return The player name. + */ + public static String decodeBase37(long l) { + int i = 0; + char[] chars = new char[12]; + while (l != 0L) { + long tmp = l; + l /= 37L; + chars[11 - i++] = NAME_CHARS[(int) (tmp - l * 37L)]; + } + return new String(chars, 12 - i, i); + } + + /** + * Default private constructor to prevent instantiation. + */ + private NameUtil() { + + } + +} diff --git a/src/org/apollo/util/NamedThreadFactory.java b/src/org/apollo/util/NamedThreadFactory.java new file mode 100644 index 000000000..e5d37a022 --- /dev/null +++ b/src/org/apollo/util/NamedThreadFactory.java @@ -0,0 +1,40 @@ +package org.apollo.util; + +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * A {@link ThreadFactory} which gives each thread a unique name made up of the + * name supplied in the constructor and postfixed with an id. + *

+ * For example, if the name {@code MyThread} was given and a third thread was + * created by the factory, the resulting name would be {@code MyThread [id=2]}. + * @author Graham + */ +public final class NamedThreadFactory implements ThreadFactory { + + /** + * The unique name. + */ + private final String name; + + /** + * The next id. + */ + private AtomicInteger id = new AtomicInteger(0); + + /** + * Creates the named thread factory. + * @param name The unique name. + */ + public NamedThreadFactory(String name) { + this.name = name; + } + + @Override + public Thread newThread(Runnable runnable) { + int currentId = id.getAndIncrement(); + return new Thread(runnable, name + " [id=" + currentId + "]"); + } + +} diff --git a/src/org/apollo/util/StatefulFrameDecoder.java b/src/org/apollo/util/StatefulFrameDecoder.java new file mode 100644 index 000000000..f16bdba5c --- /dev/null +++ b/src/org/apollo/util/StatefulFrameDecoder.java @@ -0,0 +1,118 @@ +package org.apollo.util; + +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.handler.codec.frame.FrameDecoder; +import org.jboss.netty.handler.codec.replay.ReplayingDecoder; +import org.jboss.netty.handler.codec.replay.VoidEnum; + +/** + * A stateful implementation of a {@link FrameDecoder} which may be + * extended and used by other classes. The current state is tracked by this + * class and is a user-specified enumeration. + * + * The state may be changed by calling the + * {@link StatefulFrameDecoder#setState(Enum)} method. + * + * The current state is supplied as a parameter in the + * {@link StatefulFrameDecoder#decode(ChannelHandlerContext, Channel, ChannelBuffer, Enum)} + * and + * {@link StatefulFrameDecoder#decodeLast(ChannelHandlerContext, Channel, ChannelBuffer, Enum)} + * methods. + * + * This class is not thread safe: it is recommended that the state is only set + * in the decode methods overriden. + * + * {@code null} states are not permitted. This means you cannot use + * {@link VoidEnum} like used in a {@link ReplayingDecoder}. If you do not need + * state management, the {@link FrameDecoder} class should be used instead. + * @author Graham + * @param The state enumeration. + */ +public abstract class StatefulFrameDecoder> extends FrameDecoder { + + /** + * The current state. + */ + private T state; + + /** + * Creates the stateful frame decoder with the specified initial state. + * @param state The initial state. + * @throws NullPointerException if the state is {@code null}. + */ + public StatefulFrameDecoder(T state) { + this(state, false); + } + + /** + * Creates the stateful frame decoder with the specified initial state and + * unwrap flag. + * @param state The initial state. + * @param unwrap The unwrap flag. + * @throws NullPointerException if the state is {@code null}. + */ + public StatefulFrameDecoder(T state, boolean unwrap) { + super(unwrap); + setState(state); + } + + @Override + protected final Object decode(ChannelHandlerContext ctx, Channel channel, + ChannelBuffer buffer) throws Exception { + return decode(ctx, channel, buffer, state); + } + + @Override + protected final Object decodeLast(ChannelHandlerContext ctx, Channel channel, + ChannelBuffer buffer) throws Exception { + return decodeLast(ctx, channel, buffer, state); + } + + /** + * Sets a new state. + * @param state The new state. + * @throws NullPointerException if the state is {@code null}. + */ + public final void setState(T state) { + if (state == null) { + throw new NullPointerException("state"); + } + this.state = state; + } + + /** + * Decodes the received packets into a frame. + * @param ctx The current context of this handler. + * @param channel The channel. + * @param buffer The cumulative buffer, which may contain zero or more + * bytes. + * @param state The current state. The state may be changed by calling + * {@link #setState(Enum)}. + * @return The decoded frame, or {@code null} if not enough data was + * received. + * @throws Exception if an error occurs during decoding. + */ + protected abstract Object decode(ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer, T state) throws Exception; + + /** + * Decodes remaining data before the channel is closed into a frame. You + * may override this method, but it is not required. If you do not, + * remaining data will be discarded! + * @param ctx The current context of this handler. + * @param channel The channel. + * @param buffer The cumulative buffer, which may contain zero or more + * bytes. + * @param state The current state. The state may be changed by calling + * {@link #setState(Enum)}. + * @return The decoded frame, or {@code null} if not enough data was + * received. + * @throws Exception if an error occurs during decoding. + */ + protected Object decodeLast(ChannelHandlerContext ctx, Channel channel, + ChannelBuffer buffer, T state) throws Exception { + return null; + } + +} diff --git a/src/org/apollo/util/StreamUtil.java b/src/org/apollo/util/StreamUtil.java new file mode 100644 index 000000000..5de1a78a5 --- /dev/null +++ b/src/org/apollo/util/StreamUtil.java @@ -0,0 +1,49 @@ +package org.apollo.util; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * A class which contains {@link InputStream}- and {@link OutputStream}-related + * utility methods. + * @author Graham + */ +public final class StreamUtil { + + /** + * Writes a string to the specified output stream. + * @param os The output stream. + * @param str The string. + * @throws IOException if an I/O error occurs. + */ + public static void writeString(OutputStream os, String str) throws IOException { + for (char c : str.toCharArray()) { + os.write(c); + } + os.write('\0'); + } + + /** + * Reads a string from the specified input stream. + * @param is The input stream. + * @return The string. + * @throws IOException if an I/O error occurs. + */ + public static String readString(InputStream is) throws IOException { + StringBuilder builder = new StringBuilder(); + int character; + while ((character = is.read()) != -1 && character != '\0') { + builder.append((char) character); + } + return builder.toString(); + } + + /** + * Default private constructor to prevent instantiation. + */ + private StreamUtil() { + + } + +} diff --git a/src/org/apollo/util/TextUtil.java b/src/org/apollo/util/TextUtil.java new file mode 100644 index 000000000..6d035e82a --- /dev/null +++ b/src/org/apollo/util/TextUtil.java @@ -0,0 +1,150 @@ +package org.apollo.util; + +/** + * A class which contains text-related utility methods. + * @author Graham + */ +public final class TextUtil { + + /** + * An array of characters ordered by frequency - the elements with lower + * indices (generally) appear more often in chat messages. + */ + public static final char[] FREQUENCY_ORDERED_CHARS = { + ' ', 'e', 't', 'a', 'o', 'i', 'h', 'n', 's', 'r', 'd', 'l', 'u', + 'm', 'w', 'c', 'y', 'f', 'g', 'p', 'b', 'v', 'k', 'x', 'j', 'q', + 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ' ', '!', + '?', '.', ',', ':', ';', '(', ')', '-', '&', '*', '\\', '\'', + '@', '#', '+', '=', '\243', '$', '%', '"', '[', ']' + }; + + /** + * Uncompresses the compressed data ({@code in}) with the length + * ({@code len}) and returns the uncompressed {@link String}. + * @param in The compressed input data. + * @param len The length. + * @return The uncompressed {@link String}. + */ + public static String uncompress(byte[] in, int len) { + byte[] out = new byte[4096]; + int outPos = 0; + int carry = -1; + + for (int i = 0; i < (len * 2); i++) { + int tblPos = in[i / 2] >> (4 - 4 * (i % 2)) & 0xF; + if (carry == -1) { + if (tblPos < 13) { + out[outPos++] = (byte) FREQUENCY_ORDERED_CHARS[tblPos]; + } else { + carry = tblPos; + } + } else { + out[outPos++] = (byte) FREQUENCY_ORDERED_CHARS[((carry << 4) + tblPos) - 195]; + carry = -1; + } + } + return new String(out, 0, outPos); + } + + /** + * Compresses the input text ({@code in}) and places the result in the + * {@code out} array. + * @param in The input text. + * @param out The output array. + * @return The number of bytes written to the output array. + */ + public static int compress(String in, byte[] out) { + if (in.length() > 80) { + in = in.substring(0, 80); + } + in = in.toLowerCase(); + + int carry = -1; + int outPos = 0; + for (int inPos = 0; inPos < in.length(); inPos++) { + char c = in.charAt(inPos); + int tblPos = 0; + for (int i = 0; i < FREQUENCY_ORDERED_CHARS.length; i++) { + if (c == FREQUENCY_ORDERED_CHARS[i]) { + tblPos = i; + break; + } + } + if (tblPos > 12) { + tblPos += 195; + } + if (carry == -1) { + if (tblPos < 13) { + carry = tblPos; + } else { + out[outPos++] = (byte) tblPos; + } + } else if (tblPos < 13) { + out[outPos++] = (byte) ((carry << 4) + tblPos); + carry = -1; + } else { + out[outPos++] = (byte) ((carry << 4) + (tblPos >> 4)); + carry = tblPos & 0xF; + } + } + if (carry != -1) { + out[outPos++] = (byte) (carry << 4); + } + return outPos; + } + + /** + * Filters invalid characters from the specified string. + * @param str The input string. + * @return The filtered string. + */ + public static String filterInvalidCharacters(String str) { + StringBuilder bldr = new StringBuilder(); + for (char c : str.toLowerCase().toCharArray()) { + for (char validChar : FREQUENCY_ORDERED_CHARS) { + if (c == validChar) { + bldr.append((char) c); + break; + } + } + } + return bldr.toString(); + } + + /** + * Capitalizes the string correctly. + * @param str The input string. + * @return The string with correct capitalization. + */ + public static String capitalize(String str) { + char[] chars = str.toCharArray(); + boolean sentenceStart = true; + for (int i = 0; i < chars.length; i++) { + char c = chars[i]; + if (sentenceStart) { + if (c >= 'a' && c <= 'z') { + chars[i] -= 0x20; + sentenceStart = false; + } else if (c >= 'A' && c <= 'Z') { + sentenceStart = false; + } + } else { + if (c >= 'A' && c <= 'Z') { + chars[i] += 0x20; + } + } + if (c == '.' || c == '!' || c == '?') { + sentenceStart = true; + } + } + return new String(chars, 0, chars.length); + } + + /** + * Default private constructor to prevent instantiation. + */ + private TextUtil() { + + } + +} diff --git a/src/org/apollo/util/package-info.java b/src/org/apollo/util/package-info.java new file mode 100644 index 000000000..10d7f7b21 --- /dev/null +++ b/src/org/apollo/util/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains utility classes. + */ +package org.apollo.util; diff --git a/src/org/apollo/util/plugin/DependencyException.java b/src/org/apollo/util/plugin/DependencyException.java new file mode 100644 index 000000000..760783f89 --- /dev/null +++ b/src/org/apollo/util/plugin/DependencyException.java @@ -0,0 +1,19 @@ +package org.apollo.util.plugin; + +/** + * An {@link Exception} which is thrown when a dependency cannot be resolved, + * or when there is a circular dependency. + * @author Graham + */ +@SuppressWarnings("serial") +public final class DependencyException extends Exception { + + /** + * Creates the dependency exception. + * @param s The message describing what happened. + */ + public DependencyException(String s) { + super(s); + } + +} diff --git a/src/org/apollo/util/plugin/PluginContext.java b/src/org/apollo/util/plugin/PluginContext.java new file mode 100644 index 000000000..82ff5904b --- /dev/null +++ b/src/org/apollo/util/plugin/PluginContext.java @@ -0,0 +1,89 @@ +package org.apollo.util.plugin; + +import org.apollo.ServerContext; +import org.apollo.game.GameService; +import org.apollo.game.command.CommandListener; +import org.apollo.game.event.Event; +import org.apollo.game.event.handler.EventHandler; +import org.apollo.game.event.handler.chain.EventHandlerChain; +import org.apollo.game.event.handler.chain.EventHandlerChainGroup; +import org.apollo.game.model.World; +import org.apollo.net.release.EventDecoder; +import org.apollo.net.release.EventEncoder; +import org.apollo.net.release.Release; + +/** + * The {@link PluginContext} contains methods a plugin can use to interface + * with the server, for example, by adding {@link EventHandler}s to + * {@link EventHandlerChain}s. + * @author Graham + */ +public final class PluginContext { + + /** + * The server context. + */ + private final ServerContext context; + + /** + * Creates the plugin context. + * @param context The server context. + */ + public PluginContext(ServerContext context) { + this.context = context; + } + + /** + * Adds a command listener. + * @param name The name of the listener. + * @param listener The listener. + */ + public void addCommandListener(String name, CommandListener listener) { + World.getWorld().getCommandDispatcher().register(name, listener); // TODO best way? + } + + /** + * Adds an event encoder. + * @param The type of encoder. + * @param releaseNo The release number. + * @param event The event. + * @param encoder The event encoder. + */ + public void addEventEncoder(int releaseNo, Class event, EventEncoder encoder) { + Release release = context.getRelease(); + if (release.getReleaseNumber() != releaseNo) { + return; + } + + release.register(event, encoder); + } + + /** + * Adds an event decoder. + * @param The type of decoder. + * @param releaseNo The release number. + * @param opcode The opcode. + * @param decoder The event decoder. + */ + public void addEventDecoder(int releaseNo, int opcode, EventDecoder decoder) { + Release release = context.getRelease(); + if (release.getReleaseNumber() != releaseNo) { + return; + } + + release.register(opcode, decoder); + } + + /** + * Adds an event handler to the end of the chain. + * @param The type of event. + * @param event The event. + * @param handler The handler. + */ + public void addLastEventHandler(Class event, EventHandler handler) { + EventHandlerChainGroup chains = context.getService(GameService.class).getEventHandlerChains(); + EventHandlerChain chain = chains.getChain(event); + chain.addLast(handler); + } + +} diff --git a/src/org/apollo/util/plugin/PluginEnvironment.java b/src/org/apollo/util/plugin/PluginEnvironment.java new file mode 100644 index 000000000..ebd677add --- /dev/null +++ b/src/org/apollo/util/plugin/PluginEnvironment.java @@ -0,0 +1,25 @@ +package org.apollo.util.plugin; + +import java.io.InputStream; + +/** + * Represents some sort of environment that plugins could be executed in, e.g. + * {@code javax.script} or Jython. + * @author Graham + */ +public interface PluginEnvironment { + + /** + * Parses the input stream. + * @param is The input stream. + * @param name The name of the file. + */ + public void parse(InputStream is, String name); + + /** + * Sets the context for this environment. + * @param context The context. + */ + public void setContext(PluginContext context); + +} diff --git a/src/org/apollo/util/plugin/PluginManager.java b/src/org/apollo/util/plugin/PluginManager.java new file mode 100644 index 000000000..456c45a1c --- /dev/null +++ b/src/org/apollo/util/plugin/PluginManager.java @@ -0,0 +1,167 @@ +package org.apollo.util.plugin; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + +import org.apollo.io.PluginMetaDataParser; +import org.xml.sax.SAXException; + +/** + * A class which manages plugins. + * @author Graham + */ +public final class PluginManager { + + /** + * The plugin context. + */ + private final PluginContext context; + + /** + * A set of all author names. + */ + private SortedSet authors = new TreeSet(); + + /** + * Creates the plugin manager. + * @param context The plugin context. + */ + public PluginManager(PluginContext context) { + this.context = context; + initAuthors(); + } + + /** + * Populates the list with the authors of the Apollo core. + */ + private void initAuthors() { + // Please don't remove or add names here, unless that person worked on + // the Apollo core! See the notice in CreditsCommandListener for more + // information about why we think you shouldn't (tl;dr we put loads of + // effort into it for free, keeping the credits intact is the least you + // can do in return). + // + // If you want to add your own name, make a plugin and add your name + // to the plugin.xml file. + // + // Thank you! + authors.add("Graham"); + authors.add("Blake"); + } + + /** + * Creates an iterator for the set of authors. + * @return The iterator. + */ + public Iterator createAuthorsIterator() { + return authors.iterator(); + } + + /** + * Starts the plugin system by finding and loading all the plugins. + * @throws SAXException if a SAX error occurs. + * @throws IOException if an I/O error occurs. + * @throws DependencyException if a dependency could not be resolved. + */ + public void start() throws IOException, SAXException, DependencyException { + Map plugins = createMap(findPlugins()); + Set started = new HashSet(); + + PluginEnvironment env = new RubyPluginEnvironment(); // TODO isolate plugins if possible in the future! + env.setContext(context); + + for (PluginMetaData plugin : plugins.values()) { + start(env, plugin, plugins, started); + } + } + + /** + * Finds plugins and loads their meta data. + * @return A collection of plugin meta data objects. + * @throws IOException if an I/O error occurs. + * @throws SAXException if a SAX error occurs. + */ + private Collection findPlugins() throws IOException, SAXException { + Collection plugins = new ArrayList(); + File dir = new File("./data/plugins"); + for (File plugin : dir.listFiles()) { + if (plugin.isDirectory() && !plugin.getName().startsWith(".")) { + File xml = new File(plugin, "plugin.xml"); + if (xml.exists()) { + InputStream is = new FileInputStream(xml); + try { + PluginMetaDataParser parser = new PluginMetaDataParser(is); + PluginMetaData meta = parser.parse(); + for (String author : meta.getAuthors()) { + authors.add(author); + } + plugins.add(meta); + } finally { + is.close(); + } + } + } + } + return Collections.unmodifiableCollection(plugins); + } + + /** + * Starts a specific plugin. + * @param env The environment. + * @param plugin The plugin. + * @param plugins The plugin map. + * @param started A set of started plugins. + * @throws DependencyException if a dependency error occurs. + * @throws IOException if an I/O error occurs. + */ + private void start(PluginEnvironment env, PluginMetaData plugin, Map plugins, Set started) throws DependencyException, IOException { + // TODO check for cyclic dependencies! this way just won't cut it, we need an exception + if (started.contains(plugin)) { + return; + } + started.add(plugin); + + for (String dependencyId : plugin.getDependencies()) { + PluginMetaData dependency = plugins.get(dependencyId); + if (dependency == null) { + throw new DependencyException("Unresolved dependency: " + dependencyId + "."); + } + + start(env, plugin, plugins, started); + } + + String[] scripts = plugin.getScripts(); + + for (String script : scripts) { + File f = new File("./data/plugins/" + plugin.getId() + "/" + script); // TODO get from metadata obj? + InputStream is = new FileInputStream(f); + env.parse(is, f.getAbsolutePath()); + } + } + + /** + * Creates a plugin map from a collection. + * @param plugins The plugin collection. + * @return The plugin map. + */ + private Map createMap(Collection plugins) { + Map map = new HashMap(); + for (PluginMetaData plugin : plugins) { + map.put(plugin.getId(), plugin); + } + return Collections.unmodifiableMap(map); + } + +} diff --git a/src/org/apollo/util/plugin/PluginMetaData.java b/src/org/apollo/util/plugin/PluginMetaData.java new file mode 100644 index 000000000..c6c4d018b --- /dev/null +++ b/src/org/apollo/util/plugin/PluginMetaData.java @@ -0,0 +1,120 @@ +package org.apollo.util.plugin; + +/** + * Contains attributes which describe a plugin. + * @author Graham + */ +public final class PluginMetaData { + + /** + * The plugin's id. + */ + private final String id; + + /** + * The plugin's name. + */ + private final String name; + + /** + * The plugin's description. + */ + private final String description; + + /** + * The plugin's authors. + */ + private final String[] authors; + + /** + * The plugin's scripts. + */ + private final String[] scripts; + + /** + * The plugin's dependencies. + */ + private final String[] dependencies; + + /** + * The plugin's version. + */ + private final int version; + + /** + * Creates the plugin meta data. + * @param id The plugin's id. + * @param name The plugin's name. + * @param description The plugin's description. + * @param authors The plugin's authors. + * @param scripts The plugin's scripts. + * @param dependencies The plugin's dependencies. + * @param version The plugin's version. + */ + public PluginMetaData(String id, String name, String description, String[] authors, String[] scripts, String[] dependencies, int version) { + this.id = id; + this.name = name; + this.description = description; + this.authors = authors; + this.scripts = scripts; + this.dependencies = dependencies; + this.version = version; + } + + /** + * Gets the plugin's id. + * @return The plugin's id. + */ + public String getId() { + return id; + } + + /** + * Gets the plugin's name. + * @return The plugin's name. + */ + public String getName() { + return name; + } + + /** + * Gets the plugin's description. + * @return The plugin's description. + */ + public String getDescription() { + return description; + } + + /** + * Gets the plugin's authors. + * @return The plugin's authors. + */ + public String[] getAuthors() { + return authors; + } + + /** + * Gets the plugin's scripts. + * @return The plugin's scripts. + */ + public String[] getScripts() { + return scripts; + } + + /** + * Gets the plugin's dependencies. + * @return The plugin's dependencies. + */ + public String[] getDependencies() { + return dependencies; + } + + /** + * Gets the plugin's version. + * @return The plugin's version. + */ + public int getVersion() { + return version; + } + +} diff --git a/src/org/apollo/util/plugin/RubyPluginEnvironment.java b/src/org/apollo/util/plugin/RubyPluginEnvironment.java new file mode 100644 index 000000000..74095135e --- /dev/null +++ b/src/org/apollo/util/plugin/RubyPluginEnvironment.java @@ -0,0 +1,53 @@ +package org.apollo.util.plugin; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.jruby.embed.ScriptingContainer; + +/** + * A {@link PluginEnvironment} which uses Ruby. + * @author Graham + */ +public final class RubyPluginEnvironment implements PluginEnvironment { + + /** + * The scripting container. + */ + private final ScriptingContainer container = new ScriptingContainer(); + + /** + * Creates and bootstraps the Ruby plugin environment. + * @throws IOException if an I/O error occurs during bootstrapping. + */ + public RubyPluginEnvironment() throws IOException { + parseBootstrapper(); + } + + /** + * Parses the bootstrapper. + * @throws IOException if an I/O error occurs. + */ + private void parseBootstrapper() throws IOException { + File f = new File("./data/plugins/bootstrap.rb"); + InputStream is = new FileInputStream(f); + try { + parse(is, f.getAbsolutePath()); + } finally { + is.close(); + } + } + + @Override + public void parse(InputStream is, String name) { + container.runScriptlet(is, name); + } + + @Override + public void setContext(PluginContext context) { + container.put("$ctx", context); + } + +} diff --git a/src/org/apollo/util/plugin/package-info.java b/src/org/apollo/util/plugin/package-info.java new file mode 100644 index 000000000..3b93bd8a2 --- /dev/null +++ b/src/org/apollo/util/plugin/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains classes related to plugins. + */ +package org.apollo.util.plugin; diff --git a/src/org/apollo/util/xml/XmlNode.java b/src/org/apollo/util/xml/XmlNode.java new file mode 100644 index 000000000..7caf3072f --- /dev/null +++ b/src/org/apollo/util/xml/XmlNode.java @@ -0,0 +1,221 @@ +package org.apollo.util.xml; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * A class which represents a single node in the DOM tree, maintaining + * information about its children, attributes, value and name. + * @author Graham + */ +public final class XmlNode implements Iterable { + + /** + * The name of this node. + */ + private String name; + + /** + * The value of this node, or {@code null} if it has no value. + */ + private String value; + + /** + * The attribute map. + */ + private final Map attributes = new HashMap(); + + /** + * The list of child nodes. + */ + private final List children = new ArrayList(); + + + /** + * Creates a new {@link XmlNode} with the specified name. + * @param name The name of this node. + */ + public XmlNode(String name) { + this.name = name; + } + + /** + * Gets the name of this node. + * @return The name of this node. + */ + public String getName() { + return name; + } + + /** + * Sets the name of this node. + * @param name The name of this node. + */ + public void setName(String name) { + this.name = name; + } + + /** + * Gets the value of this node. + * @return The value of this node, or {@code null} if it has no value. + */ + public String getValue() { + return value; + } + + /** + * Sets the value of this node. + * @param value The value of this node. + */ + public void setValue(String value) { + this.value = value; + } + + /** + * Removes the value of this node. + */ + public void removeValue() { + this.value = null; + } + + /** + * Checks if this node has a value. + * @return {@code true} if so, {@code false} if not. + */ + public boolean hasValue() { + return value != null; + } + + @Override + public Iterator iterator() { + return children.iterator(); + } + + /** + * Gets a {@link Collection} of child {@link XmlNode}s. + * @return The collection. + */ + public Collection getChildren() { + return Collections.unmodifiableCollection(children); + } + + /** + * Gets the first child with the specified name. + * @param name The name of the child. + * @return The {@link XmlNode} if a child was found with a matching name, + * {@code null} otherwise. + */ + public XmlNode getChild(String name) { + for (XmlNode child : children) { + if (child.getName().equals(name)) { + return child; + } + } + return null; + } + + /** + * Gets a {@link Set} of attribute names. + * @return The set of names. + */ + public Set getAttributeNames() { + return attributes.keySet(); + } + + /** + * Gets a {@link Set} of attribute map entries. + * @return The set of entries. + */ + public Set> getAttributes() { + return attributes.entrySet(); + } + + /** + * Gets an attribute by it's name. + * @param name The name of the attribute. + * @return The attribute's value, or {@code null} if it doesn't exist. + */ + public String getAttribute(String name) { + return attributes.get(name); + } + + /** + * Checks if an attribute with the specified name exists. + * @param name The attribute's name. + * @return {@code true} if an attribute with that name exists, + * {@code false} otherwise. + */ + public boolean containsAttribute(String name) { + return attributes.containsKey(name); + } + + /** + * Adds an attribute. It will overwrite an existing attribute if it exists. + * @param name The name of the attribute. + * @param value The value of the attribute. + */ + public void setAttribute(String name, String value) { + attributes.put(name, value); + } + + /** + * Removes an attribute. + * @param name The name of the attribute. + */ + public void removeAttribute(String name) { + attributes.remove(name); + } + + /** + * Removes all attributes. + */ + public void removeAllAttributes() { + attributes.clear(); + } + + /** + * Adds a child {@link XmlNode}. + * @param child The child to add. + */ + public void addChild(XmlNode child) { + children.add(child); + } + + /** + * Removes a child {@link XmlNode}. + * @param child The child to remove. + */ + public void removeChild(XmlNode child) { + children.remove(child); + } + + /** + * Removes all children. + */ + public void removeAllChildren() { + children.clear(); + } + + /** + * Gets the child count. + * @return The number of child {@link XmlNode}s. + */ + public int getChildCount() { + return children.size(); + } + + /** + * Gets the attribute count. + * @return The number of attributes. + */ + public int getAttributeCount() { + return attributes.size(); + } + +} diff --git a/src/org/apollo/util/xml/XmlParser.java b/src/org/apollo/util/xml/XmlParser.java new file mode 100644 index 000000000..a0f20f321 --- /dev/null +++ b/src/org/apollo/util/xml/XmlParser.java @@ -0,0 +1,151 @@ +package org.apollo.util.xml; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.util.Stack; + +import org.xml.sax.Attributes; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.DefaultHandler; +import org.xml.sax.helpers.XMLReaderFactory; + +/** + * A simple XML parser which uses the internal {@link org.xml.sax} API to + * create a tree of {@link XmlNode} objects. + * @author Graham + */ +public final class XmlParser { + + /** + * A class which handles SAX events. + * @author Graham + */ + private final class XmlHandler extends DefaultHandler { + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + XmlNode next = new XmlNode(localName); + + if (rootNode == null) { + rootNode = currentNode = next; + } else { + currentNode.addChild(next); + nodeStack.add(currentNode); + currentNode = next; + } + + if (attributes != null) { + int attributeCount = attributes.getLength(); + for (int i = 0; i < attributeCount; i++) { + String attribLocalName = attributes.getLocalName(i); + currentNode.setAttribute(attribLocalName, attributes.getValue(i)); + } + } + } + + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + if (!nodeStack.isEmpty()) { + currentNode = nodeStack.pop(); + } + } + + @Override + public void characters(char[] ch, int start, int length) throws SAXException { + currentNode.setValue(new String(ch, start, length)); + } + + } + + /** + * The {@link XMLReader} backing this {@link XmlParser}. + */ + private final XMLReader xmlReader; + + /** + * The SAX event handler. + */ + private final XmlHandler eventHandler; + + /** + * The current root node. + */ + private XmlNode rootNode; + + /** + * The current node. + */ + private XmlNode currentNode; + + /** + * The stack of nodes, which is used when traversing the document and going + * through child nodes. + */ + private Stack nodeStack = new Stack(); + + /** + * Creates a new xml parser. + * @throws SAXException if a SAX error occurs. + */ + public XmlParser() throws SAXException { + xmlReader = XMLReaderFactory.createXMLReader(); + eventHandler = this.new XmlHandler(); + init(); + } + + /** + * Initialises this parser. + */ + private void init() { + xmlReader.setContentHandler(eventHandler); + xmlReader.setDTDHandler(eventHandler); + xmlReader.setEntityResolver(eventHandler); + xmlReader.setErrorHandler(eventHandler); + } + + /** + * Parses XML data from the given {@link InputStream}. + * @param is The {@link InputStream}. + * @return The root {@link XmlNode}. + * @throws IOException if an I/O error occurs. + * @throws SAXException if a SAX error occurs. + */ + public XmlNode parse(InputStream is) throws IOException, SAXException { + synchronized (this) { + return parse(new InputSource(is)); + } + } + + /** + * Parses XML data from the given {@link Reader}. + * @param reader The {@link Reader}. + * @return The root {@link XmlNode}. + * @throws IOException if an I/O error occurs. + * @throws SAXException if a SAX error occurs. + */ + public XmlNode parse(Reader reader) throws IOException, SAXException { + synchronized (this) { + return parse(new InputSource(reader)); + } + } + + /** + * Parses XML data from the {@link InputSource}. + * @param source The {@link InputSource}. + * @return The root {@link XmlNode}. + * @throws IOException if an I/O error occurs. + * @throws SAXException if a SAX error occurs. + */ + private XmlNode parse(InputSource source) throws IOException, SAXException { + rootNode = null; + xmlReader.parse(source); + if (rootNode == null) { + throw new SAXException("no root element!"); + } + return rootNode; + } + +} diff --git a/src/org/apollo/util/xml/package-info.java b/src/org/apollo/util/xml/package-info.java new file mode 100644 index 000000000..875e5213d --- /dev/null +++ b/src/org/apollo/util/xml/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains classes which parse XML data into an object tree. + */ +package org.apollo.util.xml; diff --git a/test/org/apollo/net/codec/game/TestGamePacketEncoder.java b/test/org/apollo/net/codec/game/TestGamePacketEncoder.java new file mode 100644 index 000000000..393c698f3 --- /dev/null +++ b/test/org/apollo/net/codec/game/TestGamePacketEncoder.java @@ -0,0 +1,66 @@ +package org.apollo.net.codec.game; + +import static org.junit.Assert.*; + +import net.burtleburtle.bob.rand.IsaacRandom; + +import org.apollo.net.meta.PacketType; +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.buffer.ChannelBuffers; +import org.junit.Test; + +/** + * A test for the {@link GamePacketEncoder} class. + * @author Graham + */ +public class TestGamePacketEncoder { + + /** + * Tests the {@link GamePacketEncoder#encode(org.jboss.netty.channel.ChannelHandlerContext, org.jboss.netty.channel.Channel, Object)} + * method. + * @throws Exception if an error occurs. + */ + @Test + public void testEncode() throws Exception { + // generates 243, 141, 34, -223, 121... + IsaacRandom random = new IsaacRandom(new int[] { 0, 0, 0, 0 }); + GamePacketEncoder encoder = new GamePacketEncoder(random); + + ChannelBuffer payload = ChannelBuffers.wrappedBuffer("Hello".getBytes()); + GamePacket packet = new GamePacket(10, PacketType.FIXED, payload.copy()); + ChannelBuffer buf = (ChannelBuffer) encoder.encode(null, null, packet); + + assertEquals(6, buf.readableBytes()); + assertEquals(253, buf.readUnsignedByte()); + assertEquals('H', buf.readUnsignedByte()); + assertEquals('e', buf.readUnsignedByte()); + assertEquals('l', buf.readUnsignedByte()); + assertEquals('l', buf.readUnsignedByte()); + assertEquals('o', buf.readUnsignedByte()); + + packet = new GamePacket(9, PacketType.VARIABLE_BYTE, payload.copy()); + buf = (ChannelBuffer) encoder.encode(null, null, packet); + + assertEquals(7, buf.readableBytes()); + assertEquals(150, buf.readUnsignedByte()); + assertEquals(5, buf.readUnsignedByte()); + assertEquals('H', buf.readUnsignedByte()); + assertEquals('e', buf.readUnsignedByte()); + assertEquals('l', buf.readUnsignedByte()); + assertEquals('l', buf.readUnsignedByte()); + assertEquals('o', buf.readUnsignedByte()); + + packet = new GamePacket(0, PacketType.VARIABLE_SHORT, payload.copy()); + buf = (ChannelBuffer) encoder.encode(null, null, packet); + + assertEquals(8, buf.readableBytes()); + assertEquals(34, buf.readUnsignedByte()); + assertEquals(5, buf.readUnsignedShort()); + assertEquals('H', buf.readUnsignedByte()); + assertEquals('e', buf.readUnsignedByte()); + assertEquals('l', buf.readUnsignedByte()); + assertEquals('l', buf.readUnsignedByte()); + assertEquals('o', buf.readUnsignedByte()); + } + +} diff --git a/test/org/apollo/util/TestByteBufferUtil.java b/test/org/apollo/util/TestByteBufferUtil.java new file mode 100644 index 000000000..2c6baab31 --- /dev/null +++ b/test/org/apollo/util/TestByteBufferUtil.java @@ -0,0 +1,49 @@ +package org.apollo.util; + +import static org.junit.Assert.*; + +import java.nio.ByteBuffer; + +import org.apollo.net.NetworkConstants; +import org.junit.Test; + +/** + * A test for the {@link ByteBufferUtil} class. + * @author Graham + */ +public class TestByteBufferUtil { + + /** + * Tests the {@link ByteBufferUtil#readUnsignedTriByte(ByteBuffer)} method. + */ + @Test + public void testReadUnsignedTriByte() { + ByteBuffer buf = ByteBuffer.allocate(3); + buf.put((byte) 123); + buf.put((byte) 45); + buf.put((byte) 67); + buf.flip(); + + assertEquals(8072515, ByteBufferUtil.readUnsignedTriByte(buf)); + } + + /** + * Tests the {@link ByteBufferUtil#readString(ByteBuffer)} method. + */ + @Test + public void testReadString() { + ByteBuffer buf = ByteBuffer.allocate(8); + buf.put((byte) 'h'); + buf.put((byte) 'e'); + buf.put((byte) 'l'); + buf.put((byte) 'l'); + buf.put((byte) 'o'); + buf.put((byte) NetworkConstants.STRING_TERMINATOR); + buf.put((byte) 66); + buf.put((byte) 6); + buf.flip(); + + assertEquals("hello", ByteBufferUtil.readString(buf)); + } + +} diff --git a/test/org/apollo/util/TestChannelBufferUtil.java b/test/org/apollo/util/TestChannelBufferUtil.java new file mode 100644 index 000000000..d99d3f865 --- /dev/null +++ b/test/org/apollo/util/TestChannelBufferUtil.java @@ -0,0 +1,44 @@ +package org.apollo.util; + +import static org.junit.Assert.*; + +import org.apollo.util.ChannelBufferUtil; +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.buffer.ChannelBuffers; +import org.junit.Test; + +/** + * A test for the {@link ChannelBufferUtil} class. + * @author Graham + */ +public final class TestChannelBufferUtil { + + /** + * Test the {@link ChannelBufferUtil#readString(ChannelBuffer)} + * method. + */ + @Test + public void testReadString() { + ChannelBuffer buf = ChannelBuffers.buffer(6); + buf.writeBytes(new byte[] { 'H', 'e', 'l', 'l', 'o', 10 }); + String str = ChannelBufferUtil.readString(buf); + assertEquals("Hello", str); + + buf = ChannelBuffers.buffer(5); + buf.writeBytes(new byte[] { 'W', 'o', 'r', 'l', 'd' }); + str = ChannelBufferUtil.readString(buf); + assertEquals("World", str); + + buf = ChannelBuffers.buffer(3); + buf.writeByte('!'); + buf.writeByte(10); + buf.writeByte('.'); + + str = ChannelBufferUtil.readString(buf); + assertEquals("!", str); + + str = ChannelBufferUtil.readString(buf); + assertEquals(".", str); + } + +} diff --git a/test/org/apollo/util/TestCompressionUtil.java b/test/org/apollo/util/TestCompressionUtil.java new file mode 100644 index 000000000..bd86f3777 --- /dev/null +++ b/test/org/apollo/util/TestCompressionUtil.java @@ -0,0 +1,43 @@ +package org.apollo.util; + +import static org.junit.Assert.*; + +import java.io.IOException; + +import org.junit.Test; + +/** + * A test for the {@link CompressionUtil} class. + * @author Graham + */ +public class TestCompressionUtil { + + /** + * Tests the {@link CompressionUtil#gzip(byte[])} and + * {@link CompressionUtil#ungzip(byte[], byte[])} methods. + * @throws IOException if an I/O error occurs. + */ + @Test + public void testGzip() throws IOException { + String str = "Hello, World!"; + byte[] data = str.getBytes(); + byte[] compressed = CompressionUtil.gzip(data); + CompressionUtil.ungzip(compressed, data); + assertEquals(str, new String(data)); + } + + /** + * Tests the {@link CompressionUtil#bzip2(byte[])} and + * {@link CompressionUtil#unbzip2(byte[], byte[])} methods. + * @throws IOException if an I/O error occurs. + */ + @Test + public void testBzip2() throws IOException { + String str = "Hello, World!"; + byte[] data = str.getBytes(); + byte[] compressed = CompressionUtil.bzip2(data); + CompressionUtil.unbzip2(compressed, data); + assertEquals(str, new String(data)); + } + +} diff --git a/test/org/apollo/util/TestLanguageUtil.java b/test/org/apollo/util/TestLanguageUtil.java new file mode 100644 index 000000000..f6f0ec9f4 --- /dev/null +++ b/test/org/apollo/util/TestLanguageUtil.java @@ -0,0 +1,24 @@ +package org.apollo.util; + +import static org.junit.Assert.*; + +import org.junit.Test; + +/** + * A test for the {@link LanguageUtil} class. + * @author Graham + */ +public class TestLanguageUtil { + + /** + * Tests the {@link LanguageUtil#getIndefiniteArticle(String)} method. + */ + @Test + public void testIndefiniteArticle() { + assertEquals("an", LanguageUtil.getIndefiniteArticle("apple")); + assertEquals("an", LanguageUtil.getIndefiniteArticle("urn")); + assertEquals("a", LanguageUtil.getIndefiniteArticle("nose")); + assertEquals("a", LanguageUtil.getIndefiniteArticle("foot")); + } + +} diff --git a/test/org/apollo/util/TestTextUtil.java b/test/org/apollo/util/TestTextUtil.java new file mode 100644 index 000000000..a8ecd5d69 --- /dev/null +++ b/test/org/apollo/util/TestTextUtil.java @@ -0,0 +1,48 @@ +package org.apollo.util; + +import static org.junit.Assert.*; + +import org.junit.Test; + +/** + * A test for the {@link TextUtil} class. + * @author Graham + */ +public class TestTextUtil { + + /** + * Tests the {@link TextUtil#compress(String, byte[])} and + * {@link TextUtil#uncompress(byte[], int)} methods. + */ + @Test + public void testCompression() { + String str = "hello, world!"; + + byte[] compressed = new byte[128]; + int len = TextUtil.compress(str, compressed); + String uncompressed = TextUtil.uncompress(compressed, len); + + assertEquals(str, uncompressed); + } + + /** + * Tests the {@link TextUtil#filterInvalidCharacters(String)} method. + */ + @Test + public void testFilter() { + String str = "this contains <<< invalid characters"; + String filtered = "this contains invalid characters"; + assertEquals(filtered, TextUtil.filterInvalidCharacters(str)); + } + + /** + * Tets the {@link TextUtil#capitalize(String)} method. + */ + @Test + public void testCapitalize() { + String str = "tHiS is CRAP capitAliZation. do You AGreE? YES!"; + String capitalized = "This is crap capitalization. Do you agree? Yes!"; + assertEquals(capitalized, TextUtil.capitalize(str)); + } + +} diff --git a/test/org/apollo/util/xml/TestXmlParser.java b/test/org/apollo/util/xml/TestXmlParser.java new file mode 100644 index 000000000..9ac82d4cc --- /dev/null +++ b/test/org/apollo/util/xml/TestXmlParser.java @@ -0,0 +1,99 @@ +package org.apollo.util.xml; + +import static org.junit.Assert.*; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.io.StringReader; +import java.util.Set; + +import org.junit.Test; +import org.xml.sax.SAXException; + +/** + * A test for the {@link XmlParser} class. + * @author Graham + */ +public final class TestXmlParser { + + /** + * A test for the {@link XmlParser#parse(java.io.InputStream)} method. + * @throws SAXException if a SAX error occurs. + * @throws IOException if an I/O error occurs. + */ + @Test + public void testParseInputStream() throws SAXException, IOException { + XmlParser parser = new XmlParser(); + InputStream is = new ByteArrayInputStream("".getBytes()); + XmlNode root = parser.parse(is); + + assertEquals(root.getName(), "root"); + assertEquals(root.getAttributeCount(), 3); + assertEquals(root.getChildCount(), 1); + assertFalse(root.hasValue()); + + Set attributeNames = root.getAttributeNames(); + assertTrue(attributeNames.contains("a")); + assertTrue(attributeNames.contains("b")); + assertTrue(attributeNames.contains("c")); + assertFalse(attributeNames.contains("z")); + assertFalse(attributeNames.contains("y")); + assertFalse(attributeNames.contains("x")); + + assertEquals("1", root.getAttribute("a")); + assertEquals("2", root.getAttribute("b")); + assertEquals("3", root.getAttribute("c")); + assertNull(root.getAttribute("z")); + assertNull(root.getAttribute("y")); + assertNull(root.getAttribute("x")); + + XmlNode[] firstChild = root.getChildren().toArray(new XmlNode[1]); + assertEquals(1, firstChild.length); + assertEquals("z", firstChild[0].getName()); + + XmlNode[] secondChild = firstChild[0].getChildren().toArray(new XmlNode[1]); + assertEquals(1, secondChild.length); + assertEquals("y", secondChild[0].getName()); + + XmlNode[] thirdChild = secondChild[0].getChildren().toArray(new XmlNode[1]); + assertEquals(1, thirdChild.length); + assertEquals("x", thirdChild[0].getName()); + + assertEquals(0, thirdChild[0].getChildCount()); + } + + /** + * A test for the {@link XmlParser#parse(java.io.Reader)} method. + * @throws SAXException if a SAX error occurs. + * @throws IOException if an I/O error occurs. + */ + @Test + public void testParseReader() throws SAXException, IOException { + XmlParser parser = new XmlParser(); + Reader reader = new StringReader("123"); + XmlNode root = parser.parse(reader); + + assertEquals(root.getName(), "alphabet"); + assertEquals(root.getAttributeCount(), 0); + assertEquals(root.getChildCount(), 3); + assertFalse(root.hasValue()); + + XmlNode[] children = root.getChildren().toArray(new XmlNode[3]); + + assertEquals(children[0].getName(), "a"); + assertEquals(children[1].getName(), "b"); + assertEquals(children[2].getName(), "c"); + + assertEquals(children[0].getValue(), "1"); + assertEquals(children[1].getValue(), "2"); + assertEquals(children[2].getValue(), "3"); + + for (int i = 0; i < 3; i++) { + assertTrue(children[i].hasValue()); + assertEquals(children[i].getAttributeCount(), 0); + } + } + +} diff --git a/tools/trim.rb b/tools/trim.rb new file mode 100644 index 000000000..ed6b99992 --- /dev/null +++ b/tools/trim.rb @@ -0,0 +1,21 @@ +#!/usr/bin/env ruby +Dir.glob('**/*.{java,rb,xml,html}').each do |name| + lines = [] + file = File.new(name, 'r') + begin + while (line = file.gets) != nil + lines << line.rstrip + end + ensure + file.close + end + + file = File.new(name, 'w') + begin + lines.each do |line| + file.puts(line) + end + ensure + file.close + end +end