From 6e7c73f074e8479673f9738ec28bc725253c18a0 Mon Sep 17 00:00:00 2001 From: Sini Date: Tue, 25 Feb 2014 12:33:10 -0700 Subject: [PATCH] Initial commit. --- README.txt | 40 + agpl-3.0.txt | 661 ++++++++ data/config/dialogues.xml | 69 + data/config/ground-items.xml | 46 + data/config/npc-combat.xml | 36 + data/config/npc-drops.xml | 47 + data/config/npc-spawns.xml | 62 + data/config/position-change.xml | 48 + data/config/world-objects.xml | 20 + data/database/osiris_sql_structure.sql | 202 +++ data/database/osirisdb.sqlite | Bin 0 -> 19456 bytes data/itemdefs/itemdefinitions.bin | Bin 0 -> 2009363 bytes data/itemdefs/untradeable.txt | 1 + data/landscapekeys.bin | Bin 0 -> 23160 bytes settings.ini | 28 + src/osiris/Main.java | 264 ++++ src/osiris/ServerEngine.java | 332 ++++ src/osiris/data/NpcDropLoader.java | 72 + src/osiris/data/PlayerData.java | 411 +++++ src/osiris/data/SettingsLoader.java | 186 +++ src/osiris/data/parser/Parser.java | 154 ++ src/osiris/data/parser/ParserLookup.java | 76 + .../data/parser/impl/DialogueParser.java | 95 ++ .../data/parser/impl/GroundItemParser.java | 92 ++ .../data/parser/impl/NpcDropParser.java | 98 ++ .../data/parser/impl/NpcSpawnParser.java | 117 ++ .../parser/impl/PositionChangeParser.java | 252 +++ .../data/parser/impl/WorldObjectParser.java | 172 ++ src/osiris/data/sql/SqlConnection.java | 139 ++ src/osiris/data/sql/SqlManager.java | 161 ++ src/osiris/game/action/Action.java | 123 ++ src/osiris/game/action/DelayAction.java | 78 + src/osiris/game/action/DistancedAction.java | 119 ++ .../game/action/ItemActionListener.java | 44 + .../game/action/ItemActionListeners.java | 114 ++ .../game/action/ObjectActionListener.java | 44 + .../game/action/ObjectActionListeners.java | 139 ++ src/osiris/game/action/TickedAction.java | 183 +++ src/osiris/game/action/impl/BankAction.java | 102 ++ src/osiris/game/action/impl/CombatAction.java | 88 ++ .../action/impl/DeathItemsScreenAction.java | 75 + .../game/action/impl/DialogueAction.java | 103 ++ .../impl/DisplaySelectOptionAction.java | 98 ++ .../game/action/impl/DropItemAction.java | 81 + src/osiris/game/action/impl/EmoteAction.java | 95 ++ .../action/impl/EquipmentScreenAction.java | 78 + src/osiris/game/action/impl/FoodAction.java | 144 ++ .../game/action/impl/HomeTeleportAction.java | 103 ++ .../game/action/impl/PickupItemAction.java | 90 ++ src/osiris/game/action/impl/SkillAction.java | 242 +++ .../action/impl/SwitchAutoCastAction.java | 132 ++ .../game/action/impl/TeleportAction.java | 99 ++ src/osiris/game/action/impl/TradeAction.java | 83 + src/osiris/game/action/item/ItemAction.java | 81 + .../game/action/item/ItemClickAction.java | 67 + .../action/item/ItemOnCharacterAction.java | 81 + .../game/action/item/ItemOnItemAction.java | 98 ++ .../action/item/impl/BuryBonesListener.java | 87 + .../action/item/impl/FiremakingListener.java | 64 + .../action/object/ItemOnObjectAction.java | 94 ++ .../game/action/object/ObjectAction.java | 128 ++ .../game/action/object/ObjectSetter.java | 103 ++ .../action/object/impl/BankObjectAction.java | 56 + .../action/object/impl/CookingListener.java | 76 + .../impl/PositionChangeActionListener.java | 100 ++ .../object/impl/RockActionListener.java | 161 ++ .../object/impl/TreeActionListener.java | 201 +++ src/osiris/game/event/GameEvent.java | 83 + src/osiris/game/event/GameEventLookup.java | 116 ++ .../game/event/impl/ActionButtonEvent.java | 459 ++++++ .../game/event/impl/CastSpellEvent.java | 88 ++ src/osiris/game/event/impl/ChatEvent.java | 78 + .../game/event/impl/ChatOptionsEvent.java | 59 + src/osiris/game/event/impl/CommandEvent.java | 147 ++ src/osiris/game/event/impl/ContactsEvent.java | 80 + src/osiris/game/event/impl/ExamineEvent.java | 77 + .../game/event/impl/HdNotificationEvent.java | 59 + src/osiris/game/event/impl/IgnoredEvent.java | 57 + .../game/event/impl/ItemOnItemEvent.java | 79 + .../game/event/impl/ItemOnObjectEvent.java | 69 + .../game/event/impl/ItemSystemsEvent.java | 136 ++ .../game/event/impl/LoginRequestEvent.java | 138 ++ src/osiris/game/event/impl/LogoutEvent.java | 55 + src/osiris/game/event/impl/MovementEvent.java | 101 ++ .../game/event/impl/NpcAttackEvent.java | 71 + .../game/event/impl/NpcOptionEvent.java | 103 ++ src/osiris/game/event/impl/ObjectEvent.java | 88 ++ .../game/event/impl/OptionClickEvent.java | 176 +++ .../game/event/impl/PlayerOptionEvent.java | 95 ++ .../game/event/impl/RegionLoadedEvent.java | 58 + .../game/event/impl/ServiceRequestEvent.java | 218 +++ .../game/event/impl/UnhandledEvent.java | 66 + .../game/event/impl/UpdateRequestEvent.java | 150 ++ src/osiris/game/event/impl/ValueXEvent.java | 58 + src/osiris/game/model/Appearance.java | 169 ++ src/osiris/game/model/Character.java | 740 +++++++++ src/osiris/game/model/CharacterGroup.java | 299 ++++ .../game/model/CharacterGroupIterator.java | 114 ++ src/osiris/game/model/Contacts.java | 223 +++ src/osiris/game/model/Death.java | 262 +++ src/osiris/game/model/DeathManager.java | 194 +++ src/osiris/game/model/Direction.java | 174 ++ src/osiris/game/model/InfoUpdater.java | 146 ++ src/osiris/game/model/MovementQueue.java | 310 ++++ src/osiris/game/model/Npc.java | 248 +++ src/osiris/game/model/NpcDrop.java | 100 ++ src/osiris/game/model/NpcDropContainer.java | 101 ++ src/osiris/game/model/NpcMovement.java | 56 + src/osiris/game/model/Player.java | 1406 +++++++++++++++++ src/osiris/game/model/PlayerBonuses.java | 94 ++ src/osiris/game/model/PlayerSettings.java | 181 +++ src/osiris/game/model/Position.java | 302 ++++ src/osiris/game/model/Skills.java | 392 +++++ src/osiris/game/model/Viewable.java | 79 + src/osiris/game/model/WorldObject.java | 116 ++ src/osiris/game/model/WorldObjects.java | 105 ++ src/osiris/game/model/XValue.java | 80 + src/osiris/game/model/combat/Attack.java | 323 ++++ .../game/model/combat/CombatDefaults.java | 55 + .../game/model/combat/CombatManager.java | 502 ++++++ .../game/model/combat/CombatUtilities.java | 214 +++ .../game/model/combat/EquippedWeapon.java | 406 +++++ src/osiris/game/model/combat/Hit.java | 400 +++++ src/osiris/game/model/combat/HitResult.java | 99 ++ src/osiris/game/model/combat/HitType.java | 30 + src/osiris/game/model/combat/Projectile.java | 118 ++ .../game/model/combat/SpecialManager.java | 99 ++ .../game/model/combat/impl/MagicAttack.java | 93 ++ .../game/model/combat/impl/PlayerAttack.java | 114 ++ .../game/model/combat/missile/Arrow.java | 124 ++ .../game/model/combat/missile/Knife.java | 103 ++ .../model/combat/missile/RangedAttack.java | 96 ++ src/osiris/game/model/def/GameDef.java | 103 ++ src/osiris/game/model/def/ItemDef.java | 488 ++++++ src/osiris/game/model/def/NpcCombatDef.java | 346 ++++ src/osiris/game/model/dialogues/Dialogue.java | 182 +++ .../game/model/effect/BindingEffect.java | 87 + .../game/model/effect/BindingSpellEffect.java | 68 + .../game/model/effect/CombatEffect.java | 95 ++ .../game/model/effect/ConsumptionEffect.java | 34 + src/osiris/game/model/effect/Effect.java | 65 + .../game/model/effect/ExpiringEffect.java | 94 ++ src/osiris/game/model/effect/FoodEffect.java | 101 ++ .../game/model/effect/InCombatEffect.java | 72 + .../game/model/effect/PoisonEffect.java | 118 ++ .../game/model/effect/PrayerEffect.java | 127 ++ src/osiris/game/model/effect/StatEffect.java | 94 ++ src/osiris/game/model/ground/GroundItem.java | 227 +++ .../game/model/ground/GroundManager.java | 286 ++++ src/osiris/game/model/item/Bank.java | 324 ++++ src/osiris/game/model/item/ContainerDef.java | 79 + src/osiris/game/model/item/Item.java | 188 +++ src/osiris/game/model/item/ItemContainer.java | 604 +++++++ src/osiris/game/model/item/Trade.java | 352 +++++ src/osiris/game/model/magic/CombatSpell.java | 183 +++ src/osiris/game/model/magic/EffectSpell.java | 108 ++ .../game/model/magic/HomeTeleportSpell.java | 73 + src/osiris/game/model/magic/MagicManager.java | 169 ++ src/osiris/game/model/magic/Spell.java | 218 +++ src/osiris/game/model/magic/SpellBook.java | 64 + src/osiris/game/model/magic/StaffSpell.java | 86 + .../game/model/magic/TeleportSpell.java | 136 ++ src/osiris/game/model/skills/Cooking.java | 167 ++ src/osiris/game/model/skills/Firemaking.java | 147 ++ src/osiris/game/model/skills/Fishing.java | 239 +++ src/osiris/game/model/skills/Fletching.java | 373 +++++ src/osiris/game/model/skills/Mining.java | 278 ++++ src/osiris/game/model/skills/Prayer.java | 149 ++ src/osiris/game/model/skills/Skill.java | 48 + src/osiris/game/model/skills/Smithing.java | 137 ++ src/osiris/game/model/skills/Woodcutting.java | 318 ++++ src/osiris/game/update/NpcUpdater.java | 208 +++ src/osiris/game/update/PlayerUpdater.java | 304 ++++ src/osiris/game/update/UpdateBlock.java | 115 ++ .../game/update/UpdateBlockComparator.java | 54 + src/osiris/game/update/UpdateFlags.java | 113 ++ .../game/update/block/AnimationBlock.java | 89 ++ .../game/update/block/AppearanceBlock.java | 189 +++ src/osiris/game/update/block/ChatBlock.java | 94 ++ .../update/block/FaceToCharacterBlock.java | 64 + .../update/block/FaceToPositionBlock.java | 67 + .../game/update/block/ForcedChatBlock.java | 57 + .../game/update/block/GraphicsBlock.java | 84 + .../game/update/block/PrimaryHitBlock.java | 86 + .../game/update/block/SecondaryHitBlock.java | 74 + .../game/update/block/TransformNpcBlock.java | 56 + src/osiris/io/ByteForm.java | 49 + src/osiris/io/EventWriter.java | 909 +++++++++++ src/osiris/io/Packet.java | 134 ++ src/osiris/io/PacketHeader.java | 170 ++ src/osiris/io/PacketReader.java | 438 +++++ src/osiris/io/PacketWriter.java | 497 ++++++ src/osiris/io/ProtocolConstants.java | 45 + src/osiris/io/ValueType.java | 49 + src/osiris/net/OsirisPipelineFactory.java | 51 + src/osiris/net/OsirisUpstreamHandler.java | 154 ++ src/osiris/net/ProtocolException.java | 44 + src/osiris/net/codec/LoginRequestDecoder.java | 62 + src/osiris/net/codec/OsirisDecoderState.java | 44 + src/osiris/net/codec/OsirisPacketDecoder.java | 388 +++++ src/osiris/net/codec/OsirisPacketEncoder.java | 82 + .../net/codec/ServiceRequestDecoder.java | 60 + .../net/codec/UpdateRequestDecoder.java | 59 + src/osiris/util/AsyncTask.java | 82 + src/osiris/util/BankUtilities.java | 74 + src/osiris/util/ConsoleProxy.java | 72 + src/osiris/util/DistancedTask.java | 147 ++ src/osiris/util/LandscapeKeys.java | 93 ++ src/osiris/util/RubyInterpreter.java | 111 ++ src/osiris/util/Settings.java | 422 +++++ src/osiris/util/StopWatch.java | 82 + src/osiris/util/TickTimer.java | 70 + src/osiris/util/Utilities.java | 533 +++++++ src/ruby/bootstrap.rb | 20 + src/ruby/content/dialogue_options.rb | 75 + src/ruby/content/potion_drinking.rb | 167 ++ 216 files changed, 33230 insertions(+) create mode 100644 README.txt create mode 100644 agpl-3.0.txt create mode 100644 data/config/dialogues.xml create mode 100644 data/config/ground-items.xml create mode 100644 data/config/npc-combat.xml create mode 100644 data/config/npc-drops.xml create mode 100644 data/config/npc-spawns.xml create mode 100644 data/config/position-change.xml create mode 100644 data/config/world-objects.xml create mode 100644 data/database/osiris_sql_structure.sql create mode 100644 data/database/osirisdb.sqlite create mode 100644 data/itemdefs/itemdefinitions.bin create mode 100644 data/itemdefs/untradeable.txt create mode 100644 data/landscapekeys.bin create mode 100644 settings.ini create mode 100644 src/osiris/Main.java create mode 100644 src/osiris/ServerEngine.java create mode 100644 src/osiris/data/NpcDropLoader.java create mode 100644 src/osiris/data/PlayerData.java create mode 100644 src/osiris/data/SettingsLoader.java create mode 100644 src/osiris/data/parser/Parser.java create mode 100644 src/osiris/data/parser/ParserLookup.java create mode 100644 src/osiris/data/parser/impl/DialogueParser.java create mode 100644 src/osiris/data/parser/impl/GroundItemParser.java create mode 100644 src/osiris/data/parser/impl/NpcDropParser.java create mode 100644 src/osiris/data/parser/impl/NpcSpawnParser.java create mode 100644 src/osiris/data/parser/impl/PositionChangeParser.java create mode 100644 src/osiris/data/parser/impl/WorldObjectParser.java create mode 100644 src/osiris/data/sql/SqlConnection.java create mode 100644 src/osiris/data/sql/SqlManager.java create mode 100644 src/osiris/game/action/Action.java create mode 100644 src/osiris/game/action/DelayAction.java create mode 100644 src/osiris/game/action/DistancedAction.java create mode 100644 src/osiris/game/action/ItemActionListener.java create mode 100644 src/osiris/game/action/ItemActionListeners.java create mode 100644 src/osiris/game/action/ObjectActionListener.java create mode 100644 src/osiris/game/action/ObjectActionListeners.java create mode 100644 src/osiris/game/action/TickedAction.java create mode 100644 src/osiris/game/action/impl/BankAction.java create mode 100644 src/osiris/game/action/impl/CombatAction.java create mode 100644 src/osiris/game/action/impl/DeathItemsScreenAction.java create mode 100644 src/osiris/game/action/impl/DialogueAction.java create mode 100644 src/osiris/game/action/impl/DisplaySelectOptionAction.java create mode 100644 src/osiris/game/action/impl/DropItemAction.java create mode 100644 src/osiris/game/action/impl/EmoteAction.java create mode 100644 src/osiris/game/action/impl/EquipmentScreenAction.java create mode 100644 src/osiris/game/action/impl/FoodAction.java create mode 100644 src/osiris/game/action/impl/HomeTeleportAction.java create mode 100644 src/osiris/game/action/impl/PickupItemAction.java create mode 100644 src/osiris/game/action/impl/SkillAction.java create mode 100644 src/osiris/game/action/impl/SwitchAutoCastAction.java create mode 100644 src/osiris/game/action/impl/TeleportAction.java create mode 100644 src/osiris/game/action/impl/TradeAction.java create mode 100644 src/osiris/game/action/item/ItemAction.java create mode 100644 src/osiris/game/action/item/ItemClickAction.java create mode 100644 src/osiris/game/action/item/ItemOnCharacterAction.java create mode 100644 src/osiris/game/action/item/ItemOnItemAction.java create mode 100644 src/osiris/game/action/item/impl/BuryBonesListener.java create mode 100644 src/osiris/game/action/item/impl/FiremakingListener.java create mode 100644 src/osiris/game/action/object/ItemOnObjectAction.java create mode 100644 src/osiris/game/action/object/ObjectAction.java create mode 100644 src/osiris/game/action/object/ObjectSetter.java create mode 100644 src/osiris/game/action/object/impl/BankObjectAction.java create mode 100644 src/osiris/game/action/object/impl/CookingListener.java create mode 100644 src/osiris/game/action/object/impl/PositionChangeActionListener.java create mode 100644 src/osiris/game/action/object/impl/RockActionListener.java create mode 100644 src/osiris/game/action/object/impl/TreeActionListener.java create mode 100644 src/osiris/game/event/GameEvent.java create mode 100644 src/osiris/game/event/GameEventLookup.java create mode 100644 src/osiris/game/event/impl/ActionButtonEvent.java create mode 100644 src/osiris/game/event/impl/CastSpellEvent.java create mode 100644 src/osiris/game/event/impl/ChatEvent.java create mode 100644 src/osiris/game/event/impl/ChatOptionsEvent.java create mode 100644 src/osiris/game/event/impl/CommandEvent.java create mode 100644 src/osiris/game/event/impl/ContactsEvent.java create mode 100644 src/osiris/game/event/impl/ExamineEvent.java create mode 100644 src/osiris/game/event/impl/HdNotificationEvent.java create mode 100644 src/osiris/game/event/impl/IgnoredEvent.java create mode 100644 src/osiris/game/event/impl/ItemOnItemEvent.java create mode 100644 src/osiris/game/event/impl/ItemOnObjectEvent.java create mode 100644 src/osiris/game/event/impl/ItemSystemsEvent.java create mode 100644 src/osiris/game/event/impl/LoginRequestEvent.java create mode 100644 src/osiris/game/event/impl/LogoutEvent.java create mode 100644 src/osiris/game/event/impl/MovementEvent.java create mode 100644 src/osiris/game/event/impl/NpcAttackEvent.java create mode 100644 src/osiris/game/event/impl/NpcOptionEvent.java create mode 100644 src/osiris/game/event/impl/ObjectEvent.java create mode 100644 src/osiris/game/event/impl/OptionClickEvent.java create mode 100644 src/osiris/game/event/impl/PlayerOptionEvent.java create mode 100644 src/osiris/game/event/impl/RegionLoadedEvent.java create mode 100644 src/osiris/game/event/impl/ServiceRequestEvent.java create mode 100644 src/osiris/game/event/impl/UnhandledEvent.java create mode 100644 src/osiris/game/event/impl/UpdateRequestEvent.java create mode 100644 src/osiris/game/event/impl/ValueXEvent.java create mode 100644 src/osiris/game/model/Appearance.java create mode 100644 src/osiris/game/model/Character.java create mode 100644 src/osiris/game/model/CharacterGroup.java create mode 100644 src/osiris/game/model/CharacterGroupIterator.java create mode 100644 src/osiris/game/model/Contacts.java create mode 100644 src/osiris/game/model/Death.java create mode 100644 src/osiris/game/model/DeathManager.java create mode 100644 src/osiris/game/model/Direction.java create mode 100644 src/osiris/game/model/InfoUpdater.java create mode 100644 src/osiris/game/model/MovementQueue.java create mode 100644 src/osiris/game/model/Npc.java create mode 100644 src/osiris/game/model/NpcDrop.java create mode 100644 src/osiris/game/model/NpcDropContainer.java create mode 100644 src/osiris/game/model/NpcMovement.java create mode 100644 src/osiris/game/model/Player.java create mode 100644 src/osiris/game/model/PlayerBonuses.java create mode 100644 src/osiris/game/model/PlayerSettings.java create mode 100644 src/osiris/game/model/Position.java create mode 100644 src/osiris/game/model/Skills.java create mode 100644 src/osiris/game/model/Viewable.java create mode 100644 src/osiris/game/model/WorldObject.java create mode 100644 src/osiris/game/model/WorldObjects.java create mode 100644 src/osiris/game/model/XValue.java create mode 100644 src/osiris/game/model/combat/Attack.java create mode 100644 src/osiris/game/model/combat/CombatDefaults.java create mode 100644 src/osiris/game/model/combat/CombatManager.java create mode 100644 src/osiris/game/model/combat/CombatUtilities.java create mode 100644 src/osiris/game/model/combat/EquippedWeapon.java create mode 100644 src/osiris/game/model/combat/Hit.java create mode 100644 src/osiris/game/model/combat/HitResult.java create mode 100644 src/osiris/game/model/combat/HitType.java create mode 100644 src/osiris/game/model/combat/Projectile.java create mode 100644 src/osiris/game/model/combat/SpecialManager.java create mode 100644 src/osiris/game/model/combat/impl/MagicAttack.java create mode 100644 src/osiris/game/model/combat/impl/PlayerAttack.java create mode 100644 src/osiris/game/model/combat/missile/Arrow.java create mode 100644 src/osiris/game/model/combat/missile/Knife.java create mode 100644 src/osiris/game/model/combat/missile/RangedAttack.java create mode 100644 src/osiris/game/model/def/GameDef.java create mode 100644 src/osiris/game/model/def/ItemDef.java create mode 100644 src/osiris/game/model/def/NpcCombatDef.java create mode 100644 src/osiris/game/model/dialogues/Dialogue.java create mode 100644 src/osiris/game/model/effect/BindingEffect.java create mode 100644 src/osiris/game/model/effect/BindingSpellEffect.java create mode 100644 src/osiris/game/model/effect/CombatEffect.java create mode 100644 src/osiris/game/model/effect/ConsumptionEffect.java create mode 100644 src/osiris/game/model/effect/Effect.java create mode 100644 src/osiris/game/model/effect/ExpiringEffect.java create mode 100644 src/osiris/game/model/effect/FoodEffect.java create mode 100644 src/osiris/game/model/effect/InCombatEffect.java create mode 100644 src/osiris/game/model/effect/PoisonEffect.java create mode 100644 src/osiris/game/model/effect/PrayerEffect.java create mode 100644 src/osiris/game/model/effect/StatEffect.java create mode 100644 src/osiris/game/model/ground/GroundItem.java create mode 100644 src/osiris/game/model/ground/GroundManager.java create mode 100644 src/osiris/game/model/item/Bank.java create mode 100644 src/osiris/game/model/item/ContainerDef.java create mode 100644 src/osiris/game/model/item/Item.java create mode 100644 src/osiris/game/model/item/ItemContainer.java create mode 100644 src/osiris/game/model/item/Trade.java create mode 100644 src/osiris/game/model/magic/CombatSpell.java create mode 100644 src/osiris/game/model/magic/EffectSpell.java create mode 100644 src/osiris/game/model/magic/HomeTeleportSpell.java create mode 100644 src/osiris/game/model/magic/MagicManager.java create mode 100644 src/osiris/game/model/magic/Spell.java create mode 100644 src/osiris/game/model/magic/SpellBook.java create mode 100644 src/osiris/game/model/magic/StaffSpell.java create mode 100644 src/osiris/game/model/magic/TeleportSpell.java create mode 100644 src/osiris/game/model/skills/Cooking.java create mode 100644 src/osiris/game/model/skills/Firemaking.java create mode 100644 src/osiris/game/model/skills/Fishing.java create mode 100644 src/osiris/game/model/skills/Fletching.java create mode 100644 src/osiris/game/model/skills/Mining.java create mode 100644 src/osiris/game/model/skills/Prayer.java create mode 100644 src/osiris/game/model/skills/Skill.java create mode 100644 src/osiris/game/model/skills/Smithing.java create mode 100644 src/osiris/game/model/skills/Woodcutting.java create mode 100644 src/osiris/game/update/NpcUpdater.java create mode 100644 src/osiris/game/update/PlayerUpdater.java create mode 100644 src/osiris/game/update/UpdateBlock.java create mode 100644 src/osiris/game/update/UpdateBlockComparator.java create mode 100644 src/osiris/game/update/UpdateFlags.java create mode 100644 src/osiris/game/update/block/AnimationBlock.java create mode 100644 src/osiris/game/update/block/AppearanceBlock.java create mode 100644 src/osiris/game/update/block/ChatBlock.java create mode 100644 src/osiris/game/update/block/FaceToCharacterBlock.java create mode 100644 src/osiris/game/update/block/FaceToPositionBlock.java create mode 100644 src/osiris/game/update/block/ForcedChatBlock.java create mode 100644 src/osiris/game/update/block/GraphicsBlock.java create mode 100644 src/osiris/game/update/block/PrimaryHitBlock.java create mode 100644 src/osiris/game/update/block/SecondaryHitBlock.java create mode 100644 src/osiris/game/update/block/TransformNpcBlock.java create mode 100644 src/osiris/io/ByteForm.java create mode 100644 src/osiris/io/EventWriter.java create mode 100644 src/osiris/io/Packet.java create mode 100644 src/osiris/io/PacketHeader.java create mode 100644 src/osiris/io/PacketReader.java create mode 100644 src/osiris/io/PacketWriter.java create mode 100644 src/osiris/io/ProtocolConstants.java create mode 100644 src/osiris/io/ValueType.java create mode 100644 src/osiris/net/OsirisPipelineFactory.java create mode 100644 src/osiris/net/OsirisUpstreamHandler.java create mode 100644 src/osiris/net/ProtocolException.java create mode 100644 src/osiris/net/codec/LoginRequestDecoder.java create mode 100644 src/osiris/net/codec/OsirisDecoderState.java create mode 100644 src/osiris/net/codec/OsirisPacketDecoder.java create mode 100644 src/osiris/net/codec/OsirisPacketEncoder.java create mode 100644 src/osiris/net/codec/ServiceRequestDecoder.java create mode 100644 src/osiris/net/codec/UpdateRequestDecoder.java create mode 100644 src/osiris/util/AsyncTask.java create mode 100644 src/osiris/util/BankUtilities.java create mode 100644 src/osiris/util/ConsoleProxy.java create mode 100644 src/osiris/util/DistancedTask.java create mode 100644 src/osiris/util/LandscapeKeys.java create mode 100644 src/osiris/util/RubyInterpreter.java create mode 100644 src/osiris/util/Settings.java create mode 100644 src/osiris/util/StopWatch.java create mode 100644 src/osiris/util/TickTimer.java create mode 100644 src/osiris/util/Utilities.java create mode 100644 src/ruby/bootstrap.rb create mode 100644 src/ruby/content/dialogue_options.rb create mode 100644 src/ruby/content/potion_drinking.rb diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..3965887 --- /dev/null +++ b/README.txt @@ -0,0 +1,40 @@ + ,-----. ,--. ,--. + ' .-. ' ,---. `--',--.--.`--' ,---. + | | | |( .-' ,--.| .--',--.( .-' + ' '-' '.-' `)| || | | |.-' `) + `-----' `----' `--'`--' `--'`----' + + http://enkrona.net +-------------------------------------------------------------- + Copyright +-------------------------------------------------------------- +The Osiris emulator is copyright (C) 2010-2011 Blake Beaupain, +Garrett Woodard, and Travis Burtrum. + + +-------------------------------------------------------------- + About Osiris +-------------------------------------------------------------- +Orisis is a lightweight and high performance server base made +exclusively for the Enkrona v4 (http://enkrona.net) project. +It is designed to emulate the RuneScape #508 server. + + +-------------------------------------------------------------- + Developers +-------------------------------------------------------------- +The Osiris emulator developers are: + + * blakeman8192 + * boomer + * samuraiblood2 + + +-------------------------------------------------------------- + Thanks +-------------------------------------------------------------- +The Osiris developers give thanks to: + + * Graham + * Taharok + * Kramer diff --git a/agpl-3.0.txt b/agpl-3.0.txt new file mode 100644 index 0000000..dba13ed --- /dev/null +++ b/agpl-3.0.txt @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/data/config/dialogues.xml b/data/config/dialogues.xml new file mode 100644 index 0000000..8bbf84e --- /dev/null +++ b/data/config/dialogues.xml @@ -0,0 +1,69 @@ + + + + + + + + 12 + + Hello NAME, what brings you here? + + 2 + + + + + 12 + + Nothing much really. Just exploring crap, killing monsters. + + 3 + + + + + 12 + + Glad to see a bright young adventurer like yourself NAME. + Come by for a chat anytime you want! + + -1 + + + + + 494,44,45 + + What can I do for you today, NAME? + + 2 + + + + 494,44,45 + + I would like to open up my bank please. + Nothing today, thank you. + + -1 + + + + diff --git a/data/config/ground-items.xml b/data/config/ground-items.xml new file mode 100644 index 0000000..09a44f7 --- /dev/null +++ b/data/config/ground-items.xml @@ -0,0 +1,46 @@ + + + + + + 3076 + 3430 + + + + 3077 + 3430 + + + + 3073 + 3429 + + + + 3103 + 3435 + + + + 3103 + 3436 + + + diff --git a/data/config/npc-combat.xml b/data/config/npc-combat.xml new file mode 100644 index 0000000..ad149a6 --- /dev/null +++ b/data/config/npc-combat.xml @@ -0,0 +1,36 @@ + + + + + + + + 12 + + + + + + + diff --git a/data/config/npc-drops.xml b/data/config/npc-drops.xml new file mode 100644 index 0000000..f9c78e1 --- /dev/null +++ b/data/config/npc-drops.xml @@ -0,0 +1,47 @@ + + + + + + 12 + + + + + + 12 + + + + + 12 + + + + + + + + + + + + + + diff --git a/data/config/npc-spawns.xml b/data/config/npc-spawns.xml new file mode 100644 index 0000000..ab4c7f2 --- /dev/null +++ b/data/config/npc-spawns.xml @@ -0,0 +1,62 @@ + + + + + + + + south + + + + + south + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/config/position-change.xml b/data/config/position-change.xml new file mode 100644 index 0000000..92aed27 --- /dev/null +++ b/data/config/position-change.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/config/world-objects.xml b/data/config/world-objects.xml new file mode 100644 index 0000000..610ecf5 --- /dev/null +++ b/data/config/world-objects.xml @@ -0,0 +1,20 @@ + + + + diff --git a/data/database/osiris_sql_structure.sql b/data/database/osiris_sql_structure.sql new file mode 100644 index 0000000..19c33ee --- /dev/null +++ b/data/database/osiris_sql_structure.sql @@ -0,0 +1,202 @@ +-- phpMyAdmin SQL Dump +-- version 3.1.3.2 +-- http://www.phpmyadmin.net +-- +-- Host: localhost +-- Generation Time: Jan 22, 2011 at 01:41 PM +-- Server version: 5.1.50 +-- PHP Version: 5.3.5 + +SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO"; + +-- +-- Database: `osiris` +-- + +-- -------------------------------------------------------- + +-- +-- Table structure for table `contacts` +-- + +CREATE TABLE IF NOT EXISTS `contacts` ( + `player_id` int(11) NOT NULL, + `friends` longtext NOT NULL, + `ignores` longtext NOT NULL, + PRIMARY KEY (`player_id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1; + +-- +-- Dumping data for table `contacts` +-- + +INSERT INTO `contacts` (`player_id`, `friends`, `ignores`) VALUES +(1, '[]', '[]'); + +-- -------------------------------------------------------- + +-- +-- Table structure for table `items` +-- + +CREATE TABLE IF NOT EXISTS `items` ( + `player_id` int(11) NOT NULL, + `inventory_item_ids` longtext NOT NULL, + `inventory_item_amounts` longtext NOT NULL, + `bank_item_ids` longtext NOT NULL, + `bank_item_amounts` longtext NOT NULL, + `equipment_item_ids` longtext NOT NULL, + `equipment_item_amounts` longtext NOT NULL, + PRIMARY KEY (`player_id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1; + +-- +-- Dumping data for table `items` +-- + +INSERT INTO `items` (`player_id`, `inventory_item_ids`, `inventory_item_amounts`, `bank_item_ids`, `bank_item_amounts`, `equipment_item_ids`, `equipment_item_amounts`) VALUES +(1, '[995, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042]', '[2147000000, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]', '[-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1]', '[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]', '[-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1]', '[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]'); + +-- -------------------------------------------------------- + +-- +-- Table structure for table `players` +-- + +CREATE TABLE IF NOT EXISTS `players` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(12) NOT NULL, + `password` varchar(40) NOT NULL, + `ip` varchar(15) NOT NULL, + `status` tinyint(4) NOT NULL DEFAULT '0', + `salt` int(11) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=2 ; + +-- +-- Dumping data for table `players` +-- + +INSERT INTO `players` (`id`, `name`, `password`, `ip`, `status`, `salt`) VALUES +(1, 'boomer', '6ff132356667a5c33784466834891e3a3e76075a', '127.0.0.1', 2, -932341131); + +-- -------------------------------------------------------- + +-- +-- Table structure for table `settings` +-- + +CREATE TABLE IF NOT EXISTS `settings` ( + `player_id` int(11) NOT NULL, + `position_x` int(11) NOT NULL, + `position_y` int(11) NOT NULL, + `position_z` int(11) NOT NULL, + `run_energy` int(11) NOT NULL, + `special_energy` int(11) NOT NULL, + `attack_style` int(11) NOT NULL, + `spell_book` varchar(10) NOT NULL, + `gender` tinyint(4) NOT NULL, + `auto_retaliate` tinyint(4) NOT NULL, + `emote_status` longtext NOT NULL, + PRIMARY KEY (`player_id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1; + +-- +-- Dumping data for table `settings` +-- + +INSERT INTO `settings` (`player_id`, `position_x`, `position_y`, `position_z`, `run_energy`, `special_energy`, `attack_style`, `spell_book`, `gender`, `auto_retaliate`, `emote_status`) VALUES +(1, 3081, 3416, 0, 100, 1000, 0, 'NORMAL', 0, 0, '[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]'); + +-- -------------------------------------------------------- + +-- +-- Table structure for table `skills` +-- + +CREATE TABLE IF NOT EXISTS `skills` ( + `player_id` int(11) NOT NULL DEFAULT '0', + `attack_curlevel` int(11) NOT NULL, + `attack_maxlevel` int(11) NOT NULL, + `attack_xp` double(255,30) NOT NULL, + `defence_curlevel` int(11) NOT NULL, + `defence_maxlevel` int(11) NOT NULL, + `defence_xp` double(255,30) NOT NULL, + `strength_curlevel` int(11) NOT NULL, + `strength_maxlevel` int(11) NOT NULL, + `strength_xp` double(255,30) NOT NULL, + `hitpoints_curlevel` int(11) NOT NULL, + `hitpoints_maxlevel` int(11) NOT NULL, + `hitpoints_xp` double(255,30) NOT NULL, + `range_curlevel` int(11) NOT NULL, + `range_maxlevel` int(11) NOT NULL, + `range_xp` double(255,30) NOT NULL, + `prayer_curlevel` int(11) NOT NULL, + `prayer_maxlevel` int(11) NOT NULL, + `prayer_xp` double(255,30) NOT NULL, + `magic_curlevel` int(11) NOT NULL, + `magic_maxlevel` int(11) NOT NULL, + `magic_xp` double(255,30) NOT NULL, + `cooking_curlevel` int(11) NOT NULL, + `cooking_maxlevel` int(11) NOT NULL, + `cooking_xp` double(255,30) NOT NULL, + `woodcutting_curlevel` int(11) NOT NULL, + `woodcutting_maxlevel` int(11) NOT NULL, + `woodcutting_xp` double(255,30) NOT NULL, + `fletching_curlevel` int(11) NOT NULL, + `fletching_maxlevel` int(11) NOT NULL, + `fletching_xp` double(255,30) NOT NULL, + `fishing_curlevel` int(11) NOT NULL, + `fishing_maxlevel` int(11) NOT NULL, + `fishing_xp` double(255,30) NOT NULL, + `firemaking_curlevel` int(11) NOT NULL, + `firemaking_maxlevel` int(11) NOT NULL, + `firemaking_xp` double(255,30) NOT NULL, + `crafting_curlevel` int(11) NOT NULL, + `crafting_maxlevel` int(11) NOT NULL, + `crafting_xp` double(255,30) NOT NULL, + `smithing_curlevel` int(11) NOT NULL, + `smithing_maxlevel` int(11) NOT NULL, + `smithing_xp` double(255,30) NOT NULL, + `mining_curlevel` int(11) NOT NULL, + `mining_maxlevel` int(11) NOT NULL, + `mining_xp` double(255,30) NOT NULL, + `herblore_curlevel` int(11) NOT NULL, + `herblore_maxlevel` int(11) NOT NULL, + `herblore_xp` double(255,30) NOT NULL, + `agility_curlevel` int(11) NOT NULL, + `agility_maxlevel` int(11) NOT NULL, + `agility_xp` double(255,30) NOT NULL, + `thieving_curlevel` int(11) NOT NULL, + `thieving_maxlevel` int(11) NOT NULL, + `thieving_xp` double(255,30) NOT NULL, + `slayer_curlevel` int(11) NOT NULL, + `slayer_maxlevel` int(11) NOT NULL, + `slayer_xp` double(255,30) NOT NULL, + `farming_curlevel` int(11) NOT NULL, + `farming_maxlevel` int(11) NOT NULL, + `farming_xp` double(255,30) NOT NULL, + `runecrafting_curlevel` int(11) NOT NULL, + `runecrafting_maxlevel` int(11) NOT NULL, + `runecrafting_xp` double(255,30) NOT NULL, + `construction_curlevel` int(11) NOT NULL, + `construction_maxlevel` int(11) NOT NULL, + `construction_xp` double(255,30) NOT NULL, + `hunter_curlevel` int(11) NOT NULL, + `hunter_maxlevel` int(11) NOT NULL, + `hunter_xp` double(255,30) NOT NULL, + `summoning_curlevel` int(11) NOT NULL, + `summoning_maxlevel` int(11) NOT NULL, + `summoning_xp` double(255,30) NOT NULL, + `overall_curlevel` int(11) NOT NULL, + `overall_maxlevel` int(11) NOT NULL, + `overall_xp` double(255,30) NOT NULL, + PRIMARY KEY (`player_id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1; + +-- +-- Dumping data for table `skills` +-- + +INSERT INTO `skills` (`player_id`, `attack_curlevel`, `attack_maxlevel`, `attack_xp`, `defence_curlevel`, `defence_maxlevel`, `defence_xp`, `strength_curlevel`, `strength_maxlevel`, `strength_xp`, `hitpoints_curlevel`, `hitpoints_maxlevel`, `hitpoints_xp`, `range_curlevel`, `range_maxlevel`, `range_xp`, `prayer_curlevel`, `prayer_maxlevel`, `prayer_xp`, `magic_curlevel`, `magic_maxlevel`, `magic_xp`, `cooking_curlevel`, `cooking_maxlevel`, `cooking_xp`, `woodcutting_curlevel`, `woodcutting_maxlevel`, `woodcutting_xp`, `fletching_curlevel`, `fletching_maxlevel`, `fletching_xp`, `fishing_curlevel`, `fishing_maxlevel`, `fishing_xp`, `firemaking_curlevel`, `firemaking_maxlevel`, `firemaking_xp`, `crafting_curlevel`, `crafting_maxlevel`, `crafting_xp`, `smithing_curlevel`, `smithing_maxlevel`, `smithing_xp`, `mining_curlevel`, `mining_maxlevel`, `mining_xp`, `herblore_curlevel`, `herblore_maxlevel`, `herblore_xp`, `agility_curlevel`, `agility_maxlevel`, `agility_xp`, `thieving_curlevel`, `thieving_maxlevel`, `thieving_xp`, `slayer_curlevel`, `slayer_maxlevel`, `slayer_xp`, `farming_curlevel`, `farming_maxlevel`, `farming_xp`, `runecrafting_curlevel`, `runecrafting_maxlevel`, `runecrafting_xp`, `construction_curlevel`, `construction_maxlevel`, `construction_xp`, `hunter_curlevel`, `hunter_maxlevel`, `hunter_xp`, `summoning_curlevel`, `summoning_maxlevel`, `summoning_xp`, `overall_curlevel`, `overall_maxlevel`, `overall_xp`) VALUES +(1, 1, 1, 0.000000000000000000000000000000, 1, 1, 0.000000000000000000000000000000, 1, 1, 0.000000000000000000000000000000, 10, 10, 1154.000000000000000000000000000000, 1, 1, 0.000000000000000000000000000000, 1, 1, 0.000000000000000000000000000000, 1, 1, 0.000000000000000000000000000000, 1, 1, 0.000000000000000000000000000000, 1, 1, 0.000000000000000000000000000000, 1, 1, 0.000000000000000000000000000000, 1, 1, 0.000000000000000000000000000000, 1, 1, 0.000000000000000000000000000000, 1, 1, 0.000000000000000000000000000000, 1, 1, 0.000000000000000000000000000000, 1, 1, 0.000000000000000000000000000000, 1, 1, 0.000000000000000000000000000000, 1, 1, 0.000000000000000000000000000000, 1, 1, 0.000000000000000000000000000000, 1, 1, 0.000000000000000000000000000000, 1, 1, 0.000000000000000000000000000000, 1, 1, 0.000000000000000000000000000000, 1, 1, 0.000000000000000000000000000000, 1, 1, 0.000000000000000000000000000000, 1, 1, 0.000000000000000000000000000000, 33, 33, 1154.000000000000000000000000000000); diff --git a/data/database/osirisdb.sqlite b/data/database/osirisdb.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..15a8210e9c0c7b00406aaa10add27289b32aec1c GIT binary patch literal 19456 zcmeHO&2Jk;6rc4vacY`EDUuacg{?2Om2FukPLo_HX)&lGNlTg@ie)+L_1IZuKiu7Q znjj(MfcOiLK&k|na_8O~7moY`hyy434{)GD;_auuQa{(UD6VI{#9SAnrZ2>BP^0Q;W+ZXAP_>HKsX0s76K393WOMhaO1+00mmog@*i_k0O0ti zE^LN>$~XD@15%91t(F*1q|Tq`j^1_Tibflnd`K-jI85ExDy(f6gzdHK>jj}0#Fm7N zS`}2op>=8rx3_L?tZm&BJ}%r7*6wcKy15C7HVT{Dq9N;4aOi;}Y~I=yHt(*liw)Vf zADC9PCt7XvMA(k(H0_AvgTjYvch|QCDSI@}CsL^t_tlaoERZn`U1}4t(mEW zccpj?ifK5q;@IIa)|F>5j7A?z%~Gjx{IjSVrbT;{50WINUE}8XzXY_U`?{M*oY3kI<=jGaRg-VNgx};R{t5vDCcvYe+mHdjds8rz%b9Gr>p)0wC zZ$J9>|A_F9CP#6sq)Y?jq=8SlSrUtJT#TFJxYpF^F#;$F#6be?a=kn=_e5zCO9or(*ytfo<>2DR$_ z>4eaKl9MFSZcs&)wPCQ5C_6BrzgM=MLyh)RLROkqhPU=!xBDjI^9@_9!)!iyib9W3 zR8+a?m}QGPvZl)5yMv50DjSivD9WZLYc>_BZo2LZ-!lYmnl3L*b~bjE@Z3YfJ&c`- zU5j0Ne)~URDWE*C|8l~l8bzG%RuhTT+#L7QhHp)Ks;1et_kXI(l6L&a9U+qxgq_HB zx8v5FOfHx0uNjx5OQMSn>M2NfW`1Tmb#0C#D%81yz>notH1E3XGn8}wF$wPAQ{W8# zhqT^%%Xfp`@nj4(ZvWS7ca(-lAuhE|C_+hHl_ilfr-(;6b~(lyRMJ)cND$Op|h*feHg!$ zb@@P}`&7el2aO$}YBpgxTxMZuNnC{PRy1aDdz-emNUp3jtwGrZD1>bhwRq4_otA#MXPZgZbnGK}pJb%!v7H-sUqodc*$$M7$kbeA3C+iTt| zO&MX?scFg>}V;7;gwe zXhRsr8okrQ7Aj~S+;#nLLO93Pime}tn&?|yUVE$)We{XX7d+b@WXWdx`!2VzFyk_TP zR6mAq=U?7Cisd}k8UXuebAIFcZ5Xrvv9`UFAUxPV8~=~^@x)1ZeoTf}SOfpV`2SyT CYZY7o literal 0 HcmV?d00001 diff --git a/data/itemdefs/itemdefinitions.bin b/data/itemdefs/itemdefinitions.bin new file mode 100644 index 0000000000000000000000000000000000000000..685b0fe93981acd839b1f8f03c4315759146bb63 GIT binary patch literal 2009363 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

N*`Ox{6eQ=VlIINCKUX;dOw^pW^nYy;!cy zExR7u7nrorKEO~O5#VWA*e=7j191;-bIL*P?}69z)M-q&J4){I1|v}B?6j*TtTN}0 zy&91ck z?AK@x2!aAm4pp3gP)CE+|@@wGTjzCs*I8F+$NgJ6&R>S`j6@)d!BKAC&`B2_t1j^C- z?4mtFeug+5fC0)BNDx*vDD}NYO6^E3-e&~L>|JH-dSNxuj|Ngi1`i9zdFdwEkWv_W z^!-Lk?}&&G7=beDY6nSoa!1ogAs(=H&Yxdpn%fw z;)LNG7lE~e)4x+4)*1hP*a*%A2SmI{G7)g9hC7z;6)cca>A%pz{@x zJbaj^112_WaF*P$VedqCmNA6m8?nmnqz2VQatQ=OIx0;of3I>j{F^ zDiRJ(iL)A5zlRR!0R0$%Uc(M_z^{I6(qdc-RTVHRl)({wdK4oQfhx3)=YJen_wr-i zfm3D^h=WswJ@Il&eM)gW2h~49Kk3l<2|(P_Jw#bqQ1A106sp*pt-4qt&-ryeBJPN$ z1ScD~c=)~UCsF>@-IpJ?Iy=dmM2tn233`P4h*K8L8NJS^>lV=qi8!q~Y<&u7cd?_z zA#cP5p-gdpDBLsQ86$_n^>q`6Nc@L%0Dl^Qce4YYKUmDW7hq6Tp1}I?s(ppBV^=CT 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

PvQ_E%6p0k`sk^M z%q9Xn8PQWO2wbcz(Ni0+`Fx1FG{sCKv}fH79hoSFG9-w;PAOC9L#tZrICjS zDE9{?{>BmOqp3pqOvrvJ2lRxpUU<+Z&5YCNHRP8lt@Fdzdp_-cewv$|Nd#^>?I{z? zPvgLx(A+W1`X#a|qY3<{qkq4Uw|`@WzjV0Kt9#KDO;M1VyzJPt4X$1%uI2-Js>qn~ z3;=!6;6RmLO#w8EuSA4`jk654fF>652q5*|g~2oEJH6(Z??WM%mFtCySRU_34D;JMug;T|QO+Epdm;8mS3C?G8;7klq z6{l7mHU)4Yl?n3aa3D`Gyil%MCCR5JNLE(0CTO3_fi}T&46R-fs?wRje;x<1QVPu0VKM4kOZPvy((~t>}4Ft5`A&bQOBX}Z=5?(A~z7RUp3k@csU2S zMCbFtt=GnF2q{cD+$%T$C)k{Ol|3bLbBY~rO41q0a5!!ot%qJ&G{#HCDK7yNRw2*{ zGu=i`Ot*O}To;ro5Q{x*eqy-?tFi6&#>TB$*M#U-6(YpEk&4}RDsWvPbEhZ8xY!Hq z&~;(pmTPUZKKyD9q=}(FX9Q;iaD-9chs77=V1Eq;{sK$QilnM(+T$ z%p-|IBxU+c-u|^k-oAtL_}RCA9n0+_(*cz50!7DIwnzC zhvfl$QltxNO{hZvf%sE z@4`EOA3#0>{&^p8S$aYCxDD)rbnP-W?hqIuy0G}ngxt3R>&qFiDxG+%g0)h2ZDdGZ zK5SKc;1zT6WD{w?TSlJiw=oU0fK6df2OR(oNrX;!OX#lNOX{$gk6oNnYF2GZmu$l5 z+xgMrkifzt&RDIA>=qr@CWVUtPna_RO7E>M(x^ART;Jk*CV1b$gI6K9gpj$D54CHf z)oKm#k?Sqwed)Rs*oS;W=%Et`dlFWSyiQ_-UzfA?J2_xQ4VY3tSlW_t3cNrXiSu0? zIHR^V=v#Ir;!2%%lQw!P&`(6(g;DaH<1HwD(LISk41Wt-LgGhl$F4U_`oOz6gov(q zKz5oC+KN+fJ;y~ik>oublBj*TM7?>rkEsJ)QFovhuo1mE+8*IY>CNF8W$)#{m+021 z__Vz`q&mbdd-eM`U?yl>iYXRb^c3P)tsSdXvB`;(pF$(pW!QgCdM-0Vw@NyW2Pt4<+Cn6UiRVL$U{?#BYu{G?Rv(P=_09 zx()c@s!pdiW;9=Y5YS)23mpdc!hN;luv2~Ht6=~LAw=NDC>&W>gIA}F%zzI8=y|+A zv5;DH#BR&d2orSb=S;Bx$4lWppsX;kwCmos9_(=wUG!l94!=KbJ5U`)06h07vQfZ7 z&4JZZIUfOx3mJG*%u(3SOAb@>PN64aZ`_@P0lfwG6D8LPjD8nG+5b4=I%<1A4Y|?V z)E|Wq8wVFc<&qX#yX!p!Q>apG%`HV){#L4hj{)H(c7)4s67)tfZqJQzD)_ZT`Eh>5 z4jz~7htnB)N+ohzZjk6E-j5~5Pk_m-yi8);%oA|c!Ff?vyz<9Su(jGYk{9z&P9^6tt96kkzTY~R%sg?z@vw0|SP%rF05ul!4`80o5 z0!{P1uH-?$v=MZU6a=5)K`?9QKeveov;z<PpJf8PYjdE;0C=I7-2)3kfK0t?}-- z?PpY}9Y@JB*nt9nI?3YPOw;?Bi2$GHB0M6%q9g8c*jf@#8slB{3nqZSzyvtpyHtW9 zIcKDXBzNfAGC^jC-kQMrB0ns+i57Z1w%o}oIbd+E(ho=X`13w6!TF^^aGr9+sXHzB z+N?@@3U2MXB`Vuo3^#JEi4?c;qJuM`2;UTH9ts`@Mm;wNxp66S^IEuXL4o4M`5M1Cn4Fd#p&>#iP317@ zmUhT-b7gpOBessj#s-GTcu*j+?Q9Chl*zF9dLfdWbR9y8#JX!`utr2oXw#X*8O}=2 zXe0R>JO~vnPnOGUFbY5R^~kFwJ%XdO|Kik-!JHX&>l4zz{U)aMSN5IOPf-6=`8she zmilwnMocdI$8kUdT*wUy6h_4=8Y}1qDZjE~HL;4m1sPc-I&e4Xw(Hf+n@!sGTfqLR zzS!raU{72l?8<|%FY&Rr-Lm6tY0~BuPW)GTSkywOP799fDVTl`1YeL$ej8|B-51S} zau@$);#wA(G7L?ZQVyJjDCu4k5e(gN!s3Nc3oP<063=&l=QVxt%=SSje-qbIT6Uq& z&N#MyvHK6JTsAdbKq*FqCDDN#Y)SgpQCHBURlf_|ukDLF(ffcuaV-V+Vjs7^$G0)k zB}Y;3kakq5d?a}vF=m|O*s6feam+g9_W=KOec>lMAmAsi#o(82QwEgWMLYK(0R~Fs z)|YNTpKlaWaggzw zXWiagjQ^;(+w4$o^W)-fbFp%ppA>hSOO)IEl;<{Tgbe!s0@?q&&?zE8Gs@@+`p*06S7vJdlb2T9;)V+8>Vo>ur3?)nDyyUyQ@ z9&C-(?GAM0*>vM7_H(F@VEVCYqX?=*v1arp&$Vh|2mG3&(V`pCpea_bTOYCP7c`*y z-X`#W1HRwL%J=M_(>{9OSg236oei|Q-6EDCB3P;GJ13Ac35NDzI;%J_ftv5*u=)?s z$|Pt;qh*RAMO+=?0u_uK@ZV`06%1x8Qjp-v3L!#vkty6XG_+u&Aw4cgCRbHjCtB znA{OPjw@qOd!2L^({0*(~&h*v*v-9zsi|0x_m*PnX{r7n3J-pVSMiP%Dmt*ditpPiU zmUMeZx>I$s!ZCDx{X?z`ujj)fMd|QttBz5P1vPA`4rXa^Wr>tyU?>!rw8+2FBJWAG$cYe&tPE08G+;?d(cnmZ#9HLpyI?W}EfRQs_rPSL z#5^@x-+6vttj`@}EP;8CS*BCR!Mu*=e#Klaa(ZGp+9JD=)K{vrq5n|%PiC{^lFysZ zgJ$U?mp;@o&V@&1=V1#A4dpmwp2kwkse!J%50nvYsM?lIN|3hQso#kzI}si947EV z_0apYjY8o1HjFoBz=ii9`06r4Hq36(Fc~d@v4d)iHqKOf?(|^Yfx`-71(p=czh&gF zXcg>441QvbOkkZ@`XQsNxC9j@VqoGPdcU@H`nC-5lF~Yhl~F8`w;V7WG}Khyq$D;z zCXs;5?%4H-T2i)PuqbULCY#k*H4c8{Ezv{?+4MyuqHLImfDNOe4``c5kz5R-$Szq* z^DL5vp}#?PML1}@OIWYhx)q8Z>$+nO8l&ww9|qQCSjrB5NG{PNad~psWS%nfq|I)S zpnr*Bk08yV4;nQHH4crRpixq$m}SZk_8Vnq-bM-3n5V>~ipv%%=rKYlG9@`I<=|f} zk%~?eN}p~z!Gwt*SdI*R$f$K9EkpQIhRc#*{Q7OPCvV$?ic6}q(Y8HC2Z=OOsL!OQ z$mTP(Pjs$HilxmEX2qDu*EXII3pj+pPzr@8VWGb`^r4VlMKKe;vr#w`X{OhiUOw58 zzhYo4>(h z+}kFg7juA4@SXzN$hZ(K;NRC|_zPXX zU_`hBg>`>7dDzzc}9`pqhpr>#E1?yQrbGXh!xlF*X-~gXsdC}ooxnk3S zKH7{E2@=r1$HpnfvueZp2QAa~zG4D)B?s69({ZqR%ReHWi2$oO1W4!m*3>8a zY(#tty@Df1)S>;3O>1G#us4Ccngen|r^F!ZJx8jDCh*sAz$eDT(FrZGXiu#z!h4aR zf(%CNjf!nu;a3gpmg(D!YdKI8>6xhY9X%qZ3GE_u!DtZ`B9*UVizs3A?n5vDf^4BZ zJl@xVce+AnA{l5ujf?icAD4<+Z>mS-G$HX7LC%4m@X0(BZMeMot|b=Fi}O zozOA~*m~xpf|@XYCYK1oNJx+E(J!zS!;5xFlR^aUsNhz3Ay#@8oF6Y3blg%qobCV9 zed2kHY1m%v?;j0cDF`bVR1Cvou3z0kxN0zK^uUV@AvSk%DVlM*=q>=dPqL$1>~RNl zoe6~aPZ!)qfcq3XxOrkAH0%!iy(_On_E!o$#(2HChw4xr7;k!E{ww^7c?t=>`GZIp9zt1m9@HkT@-T~f$x792#%4w@0mE`qbqx!!fh zDciuLDgYrZ0k6XihR}Z1ew@Im-1uA#I&5udkQwL*AN&BYn}>}ArDd8o}?pI z^vO=^0M$ICAWiT6PI16z^=15kpnT@l)Wb+l+MyJ=-tBMC ze|zw;m~O9faZo0*iE<<+A<8O5y8*D$EW3_mlXTj|K^wzpt8TUqbcHLmLzz{_Fj;|G z3H=WI(81ii5yS@~g*2t#*|eXcn|m#B;=BrDeWUtjvvn=CwvARUm;D1m$YaDw5 z=v_w;7Dv4vhwriAt4wS}r$;}hh0@YB4;A%HDCh-DEK z+?WXJ=Uwg%SR`8rYW)RY5`(gVBLE%im_3GsjAN*(CpJBrF6vy$Iu5Icf%P-ISm8-o zHbON`g$ku@77z(ddqc-Tt~U`>U9sUYrB6idQ}I2MW$12z?sLQ_YT@;NM=o^r0fKSF z2$+r|Wm*|Wj8O=@~Fo`uicu*Cgv)nF+pWT@nhKu_kyE=PkcspUQ1~h^={*+R1)2lK*w-m zR?P;9vlq(_+T$+Hd|{z3SwxM=;F>X!!LZPKbM`@=K8HQkUY7M*1d%Ha$v*PvL;M1P zeG*KD)_w@`*+B5=yQM-&7rY%A;^9SetF8;TO$~WWtPNW(!ck#30;w1eEQ!dcvWr{= zB>j))g#UiVE232SxIYqo#72q$66XQn{2VJ|^WRGqVIrJZ8!polb~S(`PtPdw>tDtQ z>kCAiaNbJ7mvmhNl%Hp%i<&h-*QHvzt_6_(CnNP-;Tti!`T{Yh>(cWiT?c{k3&qoQ ziI%SG0Hpt+MK-z!h&f%C_;g(llwT~Ku8XyF9pW$%651=#%?XG(T^IXw-3fGksd&0} zXz4mUGjtIUbGml;bd7+nFBeZ2rP0Wq8;=0UYX=3Q?_C7MoUZLYT{i&bR|Z8H(JZMN zmhz%x({KKRPf`@n=TQk#OTpYFYbX47~ z5ULU&^o!CcY-wxV9Ta_|FGT?tSuav3n$Fti9)<9fj%1Lv&ppA|H`y6WSULK&&%FvE zEES0P7`)6MgEr{;R`GP{*FNQ$p^Jc+(-o|JDxmAz#nYu<`&4I!t^wCR4(R$$@pS3e zKBF^3*MMuEG0^qh;_1?_eZ~Q#e|qVx*(Ufbk0WBhwNDKwzgIk6`nAu^0Mb8Kc|b|l zi#&7IPtx_-n?S7OC&%#Zk-vr23-5x z8+84=c)Ij!pZ}Q|x&~bP+y`|1qIkOWYoGhh3|#}ReeMUkepx(S`nAvfXNIl;*FFyb zUB4=xF8$i)fipwbfNP%zfv#T{PnUk}^Wd4GYrwV7LqOMWilc&^6%N=V74h zx9oH!mL1t9>cb16Dgk1eYoAAeqTdZpks4}-^03Q};1on<93i_XFd;I$h*Axc2!z8) z*p`bJW6F_E2OW_ay49{VYN9BGNNnHkA=5D8{{vL|5X49FaGXO5eF$PgbQ3`yh35Eu zAo#RH5LH1?0d$FqsXI;Z zQDdBJAC%lDav%h`{SjV}k6RMQgxV)@0FP1&Ww;nwmZ|IuBFHPEXxgaWQpDJr#q(w;c`WYUM9%z-vaa~fK`trC^bgz%?uh!7=w4Q5aY2qg<~!mole>gS8Y zldy0^Lv+XKTp*oGY0@b{9I$t&;)wXJ3HqmUppP~{Dti6g857t zYvWWj%7^d{h-Y#L5^arCL5y01WH%WQ&*Bgw+8P_8t>F!bysa_qvGHsUL87gZDu_{Q zn2wF-a0sCo5_m~xXLtA7sL1AZe;mCVhFMk>_GEY&rdxEHKn<}roo0+o7v4F`=W?Ko z7(6K`^$9jrsbr|=^Ei-2do%b`b&J=VFEgTWn%e&P9Ku9Md#XtY`v-~QQpylThm3qDvh}=> zA3qvl@sYM&6$h;eD9#iwyT)$Yjhk&sXpVwc6{CzoFJi$M;Q$zsQE1ymYG#xd7k3*q zGs;VfyN#M3>7|9;W{a91>18~(QCEjJfjoad>a8OOTAA`kY&=vKotyHbz-37(5ZYx6 zr{m$}M2{aMQ6m4edpQU6sQyVn*RE_sK~1chSHMN~M+O&Jcl}maX#)9`Ol)UD*5*%cTB?jD_+JIe z|HMi;l>=XRHI;xai1JsnQ=S4@Z^x)2nlSzvF#cy&#!vb@W}?HGKauy-zO^XYU&~H= zKiqoHZ$em;26!EZAW_=}Nm}Ni5_;L~RA94|whvolHB#;kw?oE^i_A7H|Kp98?X*#M zxM??_dh9059x{SVoNBLUksx9@KmxKHwyKlh8~AS{4V0~FlE61&K>Q_rKmY*h%9a-5 zxdu;Uc`aFZ(s{adcm@wIg*%lp6_24Gl0(z?6(g$}Rk=RnU{IER%S9KR3;PnYLdXA7 zvICvr|7-XgycWjgn>cvE47)2Ea1NiQk6wsqBEp-&`Cl_RKM`Wjl}|duNI9_SZ}9Lq zr>}^|(9>we6#$a8tk?D>-30AhxCqyv&9S743TPt4TfyPqGASpDPr#G@`mgDI@DP*- z?@F^aLhpx^W7DQ;H|=iev}-Aq@}wgYDpO)DQa)X(wwtyG9`x<-AC@`V-^N6H_z(9( ztk?JnF|(-;ZYc)r+rj7G`%+JJsb}L!#yUNTa@BChTS`t6y?9%sx67ig$e0APXSD4$ zq+-bi889zEzJs0ZQYuK3PTr?NYXBU?1h0hVYD8lU@*S#OMV z@?-0f8;&adWM~%8-SsXWK7Abc+YgFK@0Iwau65LIHu2I+x@*)FA7^TcY4Z@JJU$wX3*IgLkL40kubjP&zp#0<9&GAY8=15ATcjBqin>5C!D4`GU z=y)oU5(jYXc7v94$XZJcLN#;b6?l(2nA6+MJ|m+}ippqUe44){(nZl%;);uJ(juQh zi_GF}5zrsD2&!0B!K{c8N~)|D)c`^TX+ggwziR^jv;3`)4&R^^rXj}X&K?8$PjOXBxkP~n~T59-yBJ4 zikpkSg2tG`+ZgGp69KvfX#TJAv!4#XSk3=6w8DwJt&na;+yMm-g=f25M;(U3%%ZRJ zH%q!a2F)UcHL)wcfo3_0p;hd^7r}KPls=y`KPfI`vF>E z0dFg$Phbd4uiEVi6eulnKK>zptE7u#&??iIkAH+#S;){T8I#_0=i?tUF`NOdsQLIO zV094#tJ(9{%+1F?Won9Sc?z75e})EG%-bNz8EnSph zEEyDjG|C`-LMvuI{w4olNQZBr`KK`-{|c?Jl(!YqCopUoOpenDn!?t`zvgd|bWsdi zWEykvZ#cwIR4iU1zIFZ)`#6G2>osQ-b;m~#{wr$GQg#|tX{=)p*{^K*Tl9!!ygdTv zf@kk@Zbf+1u!X-Cw(d4tSNBk$U6!WB`{0iHNTA;ZVS9k3{m4-zlBsmZUGRi3hbj&liB8odN2Xsq_AmS_#j#AICm}j^&I(?J z&~^(XLv@Ervs$}R(iSlx-EkD<9knZ6=*k@zS)E>!GLYB}*;s$$Cny0$AGoFMfgK`zJrWDcJOjxQH+&0{jc)uI43o zgWs3DMH0CUF1F&vzQ!oZUGP&_sm)fbliFjHAme^TrqSDh_~H z6SaDm&WN}!N}t275tCBWEu&8Rlr_E#VPSM zI7l|zdK^(I*X_#0&3JCGQHO8EFd}k09lKZWhM#!Lfi?4Z*wlm2l8LksG2{{= z4)V@9vuK{G;AtiH2Axr)rL7%v5=1SS38DyvF;P`nV{#2np3kvZiJEP7OD7-2DF~!@ z$=|0Iu~XF_hrWzY3VhkI7K5)alfksDD5dYG*D(%_PIZv&V}e`&16Z zNwQVM+AV^n6@J}>?bA50C)l3154khB$zDWh7}lK50XRXn3RtT|tU$VaOnbo3-~gUr zdj6hq+wDlI&%U99c@JqW6HK4U0XV^Q1h8I*gyQAn z+JkO!Ku>bL*RcZ~a&EviO3~DMjRELBPtbi92j(Q*DrP<1;kWbA?Lj}A1A2n)x!2m0 zvSY6a$nL`YXt{`uKqAfD{dtWbcpV4I1i=c*^R>|>zC`)R^fK` zlxAapVWfq;fru`l<$cASz+(SYxa*XQZD+LBggJ>&JChKUHCu|KMM=uOW@{n0nR|5H zbz7Yp`92WB+C}(muQCDu2NI^+llIg`#9<<_Zm$C~q|-Ue8>HI`fkK*XhpKY67jm0v zNxXvvN@Ny-E?;s~?tRk4hz#^%E5{2fZzGDa-0Pqgr&(tDH5V6xbP*+kCQK6NDJY{Q zuS^T&C550|e5i(!dJ0T9z2!DhcR-^1m*2{P^zX72G4@)+X(eXVs9~UPL?h0h8LxW{SGs5 zcl5IFh7#JJ-BA7bpBDKUnR z&?N6Qh@?LxK5U&ya@s@JQxg42Y^h_569JA?ImM)Lb`>JkaS9>iW37nFv-(<7=9?bghLhMH7aAP4qc?po4^Q0Whqzw z%!J}Sg$OZE2vLPGiyiqrZ5UBxsVIV&K-`PP$OeYRh|&$98|QmviQp1}-F=1_hb>X4 zh^|-;%BrK51+vMT6}wthV0c{At-msk_k`5u&sW+COu`2Ql=&3k0g+7ZTW}@5nT26bD{8Wyv{TxI_ zto^Y|6c)W(6 z$9@=1dEDyrcrAF`$jc)V+Q-NBmVtOY$j@UxjHWzp@p-(C17kFi0A`$%jA( zw*#m@-XxEXry8LT8z?wVhYF5OS#J4e)G#qp=+_qlc<5RbdFqxfqeOI2>mnuxrc=i= zKn_?9;jAK=Dp);W2l!4sT1#As%-0PZkF`xE`9nMss8bRsCx6zZs;B-=#ob1An;kCh zHmaw7q`2Ftp86xj-A47)-%#9bb|^0N8;iS*>Zw0k+-+1({hf=ujZ*b=%}vGKMs={? zrLfyvrfPw^7Izy}3*4=^+o)RL?#10k)dKe@?l!6xxMy*a_sKoY9+hmop*-eP%7S2kLmN#VI~iYT|!=;HZmdMOpmvza;y*- zgZ8Y+LQrw^tmB0sW9?Z}g+Mg!S+~MDx+&8+dZp?;N_&msxaw{l)ty~<#GUBgy6JeW zz6odKOJ`!g&b}3{Fj4E=68neno4Msqt%^$NvXkE%P(tSzogTgrPl^OaVw~VXm1C)1 zulm^Hzo9z4%H!EMo~b~wQ6AHZLg}EGR+W5sF)aTBEElHJ0z7}l6KTIn8}K|I&)dm+ z4&(0}`C8Nr_Fv=g>pqUy2#*$o_z^Wo!$Wa?HSC>&n3{vca7iHd!M}J>1PIOymk^1l zeXRGt5hKNZd4v=Pq##{n-ENOLNB~qD6~_!?blevxwnR{f6V|xHC3yOzQ-N@LO%(Xo z=yUOip?LV)13DVc&6{wD5p`ifN7qI+vm_1o!@s&U1OWd3MNj<8SIB?A-=9nU4`Kj% z7ZHG4$8Ed4i5kRd;AH+VPDW!tIeGv4?h$_Umv(zX%Hiv{9d1Q}P1LMk82K_&o))hZ}< z^`t{6@r;ZL-O`0ekc<|AM6`cPYeKPB97m+_S(bGzHl;UOyWO&gJiN|9vh~nx)lZ3! zLmz$C;9RSdBA_oEZgy)}*usz_FTptnP$srnrNBKeEUwi;8YZhYY8^ntSs5`#n>U+) zempwJj=@2z>jZ#azQ^w3+#Rx1*^OS^>27ckL~#et7D0wBagD5z3ObAU-e&7S7Z%Bc z)3J`(W01DhYm3!H)2(&PGGIJ`sRt?T0EtfAGwz}fbxY@k;QdLo3x zgzzT=`z7qy=UpdGoC@Tdhpi^P6M}5C%7{3xx_E~mz;Ws)2!5g9VJ`}|vopY==lv-> zoki*2q#|+1`c@QcC(`F%>mcT~Jyxs88Sbewe#fWH_#K}<<9B?~>q zs*`d&6Zn>}YHZ}!}oyxH?+@@CJ6(e#)=`AvHgi7T`84)Owi$xgtru;FpL z3@-~#79pMg0xwX#Np`R2<9{LMno9>4an`Ot$lc?V3$TZ*e^A#rB}=|FMD{$QR4{9%};^`5BuLsv}9jhFCryqJ}}NT8$g5J+MRGw<|Z zZGh!~I8Tk!Rz7AExGx2nk7p-yfq?57|5!J|CPh~IDtRY6U195YTr6TJQkcrk&`KtN zpf`$M#>24E{A3g%pmxW_YRtjQF*0r+!0M;2=HZ`-KZAdn+{MaR{^h;kF1}uFm_T~6 z*RhvDFKC4{i8U!4xBlliv(q(O4!wed7-aI_6$p@qSetkoIg5Q6NHmkCeI+=*Lxgi0 z2l3|uT#G-EB)vr2N@B}KaS4zFUOF<5?PGPxkN4Mki?oJiC!q03HH~Z z6I?zhc9rL$Cw;$L8X}d1{VBn4U7RqRj=p>?h<)^+;8bE&aO-vFIxw^#g$bFj%hl8Dt(Ow&yWrzzp=Cr3gr?1EiMw-uL7#>|Es%))g&&ElUGG`|-# zKRHAi+|S^JM|1FlbfnroEvXKKGZEr_pnG>j2IF)}P`cjbnern_h&ghqNyo=6=rBV{7c1n~?o5TMA zINdWS{HTn8FMG8IXG04+)~m(3(7Ry4`eHe@oJ>52L*UL^D=_ui4_mrBv64h$iGxf(4 z4CY1XBZ8xlT%DN+^;T#^BohHX0gCrWD4q+EXvU`4@T><}tRcl=@YAf|%D?12DjCz8-VQ38gb0GNY2*EVi zm!}X+I+sZHAEY!wrLfDtT$=s{COs#by)Mx zVbc=g3n2WOkZ_1ZliR6yu8^42Eb< zY>3mvDAj3wTKt=W>Mt@;twE{v-b$V9%Vi?Mmq7HjA<;lW8+Io{Fq-nzhpIN{I;ohD z=x|DzT?&ZqRBA|Sh>BF?1dwafjo2}3AIs0SXaT|YmzmhsK+VgxFPxt3P=v35?SmoP z)RpN8CRfV8#BADwtf*ABzZ~e1NxMC6HyX4&-EaVbj?MMLVT(lABAGkV9y+3%Q`q&* zE)q|-r}Wqb7D4}4`RPx^o>#8~;+hEYH8j9=VFRd4L%cu8_bc(&!gD>Y{TqLMuTj_l zp(AO|^MeL}TfEjRqY6s5bT&D?+BL+QQiQeJ#KsC7=2pX@f`Sxik&^K~Zb zb*S@F9|#(y9!v>$E&cT$p%C8y_1E{M{)Z6pulUhm5Yj}5Z-Mqh zeQEzUi3lpm^+hW#ikML!VUG{B<_Wt3+pvtZ-f;1rid4opiz4zwORB8~BHJA&B{m}7 z&Bg76hWR!V-&s)enutO&JCoiXT;(|6a#HcCU5lFoaHN##a{ z31FH@SXkEp&da*c+9aci5I+LzN77mMh|T=J!n);ukyw{Gy}ba&iCP=yOxS4uF&FJR z#9MW-Sh5p^QkoF|6A*txF5-#g6MS~%-;j9agA;?@qp><7NzvDl`#qR1Cmg3mlO?S| zI~I)E##zDb{jiGo=oJ6BQ06~nVm%XX-c2M=JU!bX{GWmC8v}7kS(`jUws!@-*`ZLm zbZ)V{%pYRA&Yv}J=^+J?{6G&iROUZtVp<0^uUZMDGZEq!;QMGg-w^Nbc+$kV3{P5b zl7EXP&gFR0L*aYH2OoF04S&Jr)`|BD)XD_TF#3eA@ROc+D<1E%3DRHEz0;9;VD$g@ z=1v{byy_&B&P0q~f$%#IDu!fUN{o>BlW@ix!N*8$g%gfPoJI?UkFRM`bOoLMZuD^Q z#iqrMb$z?M**b!9K_gBDMJJM)C6z^?S$@sY014y^M%;R@gM&KaeCCB%s$qRBii;fZ zj%1=(AF2<-u%$DyIJu6>&Ng!Ah@T~i?+^bsJn+>UfcW`)?e+u$z->xWz6@*px;=$s z;K&QtLAe~c<@;NnTZ_>aw)w(6Zk=+fO^!PblFFPjY*8vYDr*6vnl_4Ch_edbpZ74d)4dYdwJtO z6msLmH+glzf_tN{$Wmd^+yAkU+b`OWB3h_@;!BQ=u`%_El>JYI+*Z9?qIME_Bg;227F**+4K|~&odr5qM`!B~wW(t4bnRlS;pt`*% zKe1k(;`D1!z_4j_a4wF{%8|;gcm*gVU;iis&yp*VV5Ete@@8oLCh0GnL2~?eg(SHV-bp&Y2YFYOBgf zM>t+nT*lr8Ih1@Q8(3ccN-C-{~bQ^gZVLE%&5JianH1G>gE<64ud%QWsqSK+sw`q7tcrc3F#) zAl#+hfO*bA7dCX?Ro~hZS(x&CEPhksNYHMt)aIxptnDI7Xom=iRxVLBU>GQ{Ohj5E zQC_!(xM~xDat%aWCDC(xp+$C7&``mWjMGZ#UTkDoCqJ&riH|0Ymqx6^! zw*^^PbjpmrMCfg69BIg=%mlzz=A3&DJffUIPP<_oBtfQ-a*^NK=O#UX)#G6-hF+(W=5;6N&aZ z4J=q=;UEcw{sn;)p3ylrFH)Mr_tgbV%#vq~blY}w%z?4jYgXI_OuMc~{4pxOVxkik z@QhBM0s1S@g2u8t3FBOx-9vIf?0U;-g{Be z_r6`qy+t)+FUN7mu_v))l1w0AY)*Ob9;3EFlnIH3Xz@VU){$Cs$|A1-F+=JE8BIh+ zptT4lo>M6j&3ccU1RCL+Nx;Dd#+>T^1)Px24TWnSE;+>CCCc2Nnc{UG9&jc=Qlk+dr zk)~8~S$2_8WApk0O_(gz8vIh7J6QWsh68Tb{XM%O=$G$DlkES+)brkdPM!1@UAxADc@sHB? zVN-aTi$X>D(Jzjm-`On{1%DL}d{wo8{H3xVd)hc+2AAp@A6X^E%cVrj`k}xJ#6pPZB)_#e-zB*N5I`s83%#37v}E!=zEgWHjo! zMuszdBZJpPr*mgPBta6pK6X$M)kGhi%0#?k8iDx55>3a`@WpX|8V7tehYbR2QsC<& z3Znv>w7}^cA}I9ZC61>p8?oWsb>WG$%57(I!*(3~T)Q2i`IM3v9{lj*-S}9l_cPFw zHwda zPfgObmFU{4q3di8C{eolKryCkt54TD4vbN{?vtcz3+P(2MbOoEES|%GB+6DFB*tuQ z@!2|;17jjkXW^c9uQLv3ToVC{BJ8{(FO+NTDz+u*x`~Fp9S6+lBmoah45mI(Q5DMs zJ{<*OHo5_S(O$%Xw!7GNzSXAlpM64#)^1?Ad!!@c?(+{Iti9s8bviP3@*b3O#Q7!o z-05nsY?R`3?RhK+BaX`<@N)>oDKJqYb3fv%L{iq4cV!fXAjT;HIlRNPFYSH9q(#r? zKpgFmDTuYJSSuo;i2xUH2oP11G@^EcP@I@@yLQ(F<6^JDdk0T5zV}CH|nHR1C~(SV?FjQ5Ee*5i`aH4!?#8C zVQNSdE6TlLTN;kGv_k;V7Lf0Ri+wW(^h6JhLC-;|BC1LMypRKal=-=Ev>y^Z)1%hc z3lxl^pj1CJL3|Mh;wa($5O3Go-bp!4MA^b2N`(Ab_lS{SK_n_NyOrQ}??=Ayae3b} z0l$?4e54)vf;Vdib5XWoH8(owYA(_aq!>>^$OhGP>Xt5r57c{2^CT`i^-by3oXTx` zirg#4)tuID=V*?E42wp*;A#I*%=#lP1v8^eg69nKVyeZiHukbn>CCGmZPJf+@Bmje zG|fu;pyE+(w}nH-uu1yaY!|cKTy=AnUJ9?sZd5F)(ykh9p2K9s34rJ&Yma4aDh6WXAxwU_ds zErmIyXbdgc-9t>HFfT=j2^P;X?6d5D0GGQLezMa5?PVD8W5M^QA3~<|Iyit&Jldwz zu-y_))_6xFy^}SQ;<9Yk+vC>bgWp>1bGY?_t6Uh`-i^AYb8+ecu{+d`EqJ%aaLNRV z$NLmtJBCw*y;#yoMUv=CVddO`sdbfqn)>2My*Eb1y%0&T0Wlg=p3#cM0~LoW+jWOd z!%l6s*6*XS)a#(F*L!a8%Xt8)ECSGiYrs~cJKn30_uzN{iy)CsjReU$JGb;|Mn3O5 zvID)&iUVDV3Uw%l5u!Clc?AzjrFm)eE>o2-f`usQ#ke)e2*-_gwG2_uhMNx!!xf z%k|!Kz5Kran=P6x@_M8)uTvxX}s=^47+weui-0#55jsa#b7x}8H*V<&CNpXh?r zlZo%e;R)*wP*<6jx}C8Bjyw?K-AdcBY8!kn#!$*D2OaHXcF-QTmLop~W0zg17HYLJ zqVTLq*I9JZq@tQITA?1U2+wfS2IIqmF={~~wqrXqJ;hsmJVJnX@=z?Ls*7|eonpW9 zDzJCOU{eM_3gD%LI(`f}oS}unpykPbwi>U+PbA$$a*ea}2<1pwv~VYIzm@nS%APIV z4PolK7A}r{+k!UQls!w2`;C@L3wMJ!P5Kd~(3b8&H#q9Dw&nyadJ9IN+put8PTCPZ?K6Yc2EJ)N+ zLW&?PJ1{I3y<2|~OzV^y3ZcqNui-!rnF*6U6hoda_>q4SmYBBG38ix_B&K~eTw3a* zKe1;-zx1`EZnw+fA>qKfea1fgdM{Phppu=xZGUIm{{@B1PgP;GSqvjc&Pne*{$v;;)FlBXj*xWNBBqEf!`Ey^L`T1`aF4mj_Ji_uWU4%ZBE z2(52!weJ5HRsN>6E~!~;CJF|uBPM*DvnFzeQkGEDXkqnG-2hZIR#fEr+v|ln2~N%& zv9h8B>3I1llqzC@2us!Hr1ofR57$~QUaO)QzL zxp+e-6@Ow;EcTX4u8D_1ayMCKDGGmPzJtLidkbT-yVUs$uAvc;2S+6`ZK zm*^^dU66{V3UxQ3q9JZVhw7mY(UEG=G##Q&(NCbA zK?ZW@kJxO`p*M^B`8*vBhKn{Xpf_A+BDX_;GF^4wlS7O`y&_pYWk&&dawuDWxkzb! zX#bBWz#F_nJDh*%jt8l^#K%BSZ}RkSI_<~3Fgmvc{SFux{Lk=z#OQ33zASupq<+v1 zoCjH$A4pY+UNn)RNm$7_dU9_qBhv4W2*T2FkGlZ{+vyD8247BK;#TUk{x^cINb;m| zBXS>%gUv7wSKl5C#99iCrSub!5aB_EI|)i>co9s%(gV)9eqW9;pi^e67nl`^y-2^5 zj`v+DCl`zSNK-m_2VCZ(z8xMNVIUJ^z;YjL3x@%^v<0^xZ#b7uLgAvRP zXkKWkoqm;0$`j^f)&Qp`zrcSy5X4avWnI;l@iqKlvIz zkw!GXGNmyaMi+)sex?I`kE5X|2v+_yxnNZcpUd7qpuaDejit%(?Kcbi_& zi(n~Mmned3aED}8JO;*j+LyKayF72;ZA%Q5sq*wp=2uFN@g{kx~1m)TZD|W zRt^_}(z6|wL@0eEcFRNilQXEIQ*sHonu&9-FD#a^cU1jC{6uyLC*4AkD^0ATM~&*u zR;31DU1s$18q}&(W)V~RmJ#-UNEZOEnOSF;Y&VSYc-Bm5J9Y>jd zVD&rE0o65*PK1w3(7=P+g*C=0y9FCzQa6r z451^~n_>0!K2$;4(1Nrde$ym{3y?M{$Y269ECMm;(@udm0ep%9__h{u0AGOV4ZzqBBX@^*A+!;0 z5|9Z4cM6#NxY1D|_y}+w(}@#%SOV-C$b@$gx&X5ha=`@6rce1vz`TF({m$hel}^`= zhq81Ymx?%#0_OvC;#?uq39sw|+^yVES427mqz}}Ibh(TaKFGKL>j>yE0DaKx0YyH% z4uReuKp(6Vs6XX}j5ct_>5R|^@TmTlViKE?!O=p^tc!B&@Uw-(!@wvxS^70(-1GVa zLD7}nQxs`>{s``2u(`<0zYalQQ{LJ9L0Y<*+}4A&wAHiv(#tNt_?FCr=3MF0_YlxG zpPjx1hf9`KZ%Ha^LpG`C%aCA?&0qI!Go{Y-M155v{@b{}5T{NuV1B zyv1P-vnqaiwTA=5LuUg7j(j4P1Xi(T-5|TJN5dn4;9=|t79Jwk5Z9cH!a3-_FeT%! zLjgndA*^!bpy0uHBw#%J_``Sd45S6tYYuoW-Reb;q3 zD40s=+#aKaQRw+fMcnz*>Dqlm>fjnU=pp!Bh1;aGCU=ubtX^9SlO?SWcJEp?%9gIzs={ zS)zVJd-Qv(Y_anWJPq_SB+5yhy?%|B1i{~nq}H8C&eLa!`)TIvHI!t&Bk<1v@Bf(v zu}9kWc4EH|M@ z1pTvTiT!EBe^0XtgH0#kpMy=qBa^8|w|<1*-(zQWA$~XDm$Fq+nc^Mz4SvLZ=hgr4 z-p`ryC4^P%Mhbd5X1g_t&%{O?p-UqDQ60q~;Bp{h@#Fh23MdSM3jG#DoX>NaSeATX z$tvH93_~G}NMzXL6x?H}?1ExM*z+LH|4!!A?^4=d;#B^jjhH*da3FJW3HOMkLW)bD z&qP)D%Ci5kLuDbWV9sL$Qigb$2sx^biags(5+d#b2Ahk!+#65=RjsR>?-8T29!lsFzHp*$}= z__?X%OZ{RVuydxB;dF_RWnP8rmq4vPs(TeMN8%bP0Eq|?0EVqX7WNHT4{)o{@SZr6 z)&|_j=V4rIkn_F@*p~wAquIeO9I+-h2rS4X0-(5)ue;dDcL1hptXO8HVIhPPTQ>x) zS(8e~-IoFN=?tiYNh0n-`t}K3R=1jzGzPUOT-_$#yMgj_NN>P=N@sN1xWP`QLiWo! z1UqI==s5^ucMe(^D=qnG_;rn72uBjA28zatz!yYQ28^ZO+`a+~pB8+-^PAf+!*fww zS@<3zfz>3Dh=R;2s9woKpahESMSZxwz`Qb>h*G6-hi^-*j(8V}^K+k)1lP%kGjfAuUd4h@kGU9l+CYe4xankip5 z6WmlySB3D`&H~{V%mg?N)deM=l-GgsB_{&qNE)i-lK*-wl=qHUM^%(cu{^HiTk{6c zy_lWu5bAb1g`jq!Q{D*Vi}WIIr&2=56;;Zc^ovP5jS_;bs!`soA9p*262h&lP~HML z8PY2!Ga_77pS)EI;e$KMa3n_&9)F{9LHb4oMUv^CsH{J41L=e8q{qM`$SrZr_YilX zUEV%B?6=d6(GDmonRkHx0sZu^pCSFKy5^m;L;i*t!dF%~?*je(Cl39pn&;hG=$?)N?5zmJ{&a0f1FBRpZ$U1*^9&cY~cQR{@F7J46s zp;z-TAoFPA3eaX4R5j82XJHVws&zqG8+`!7u<*nh233vpK|RATf|8GcMw)@eCX4T@ z?1DZ7<`?iYAA;R(2SHf7&^{lY1;*PQuR_ojbOHIL(mG*%R z1mros%ljwwK$Pj)8-W-y<~KGPyD3}w|aZCNtd<3`NMAPRpHWyL6lF?g!7VtpDAAHxo@--Fmd;`ed#p!Y$F2P*hJ z1ALEV$Jcue9My5oh){TQ@KgojX94kX>=64sh#rNf2NZfBm6`k;U_PE5=HMZ_yrIRL z$QADiq+^$%_k6i*eMg1c&ja)m*g-FnK(q1t1wefwJJjC8?(7zldR8C=j+M2FNJ34Q zq4-FJ(k}w%lh|?gdpJEh;b!1VfrM09X1@fiPiDpX>-)h8YScu!+Zu&Ycu5@*5>A01 zIn-sOTs*`oDz1p&b`u#6{cn3wPhZxu5)F;u%1>$x@p*gKV7)0K$i8Ob@|f1ihJpKd z0|5$@RtkaDUVPqHz|#s=p6G&m(QdlS>k!gcwU9J;5ZkEMfT(cvHE^_&oul}uok_{P z;82fU0bD6er2d8fUDlfNhU)8Dcu5$l6dd0G9jn;sh>cGQif?LRBZgur&2Bj0z;y^) z&x9%cEwFK>el}LOaKQ0OWk6I5o>nBP8IjmHO_Tw5nS7ca_{^9b=w@T?Beug13;tK zo8q33SV5d9V6GCCM_{#pv4i%#hi&QPw*rvx2rPez_b!5uSr*^sSl6>Qy4R;ny4thJxSYU^k=&IY@m|0sk8g z@G+n8-eEEOU80f1Pta>-k%m?zZaCyC3Gua=!OaM+e+$7lzpY@jhI^V3SATuwyRZMvBUjNk5SXzKF;DQkCpZqWj4a)5 zRdH+JP|YL6z)7E}VEui2toY;~_@%lkr{R}ite|hxU;G9?0;?1QLz&Zu^CqGN9b*Ck zdPx6XpmGAI`GF2@bzJvx9P7&9KP>Qw-nv_`*eaO2a*)A6G>G*!nKENC%rp(^9cucB1$o zCvzsM8JWi@m_KgXbkzchn)1T{3}C_l;+tag5paB-t)IR2vo)?i^1MJUg>k?iNFf{5D+=>t!;{&^yR z_}7U5;@>9%2t?}lJfr)CPMYfT-i*D@NTFD+)v(!tWgPPz3v$+fQYjoMAU++>XEwh! z2t6%!2i@Wf?cI}brq*^uE-tBKDjmSO-1E$~9q0{dYWGd-S!5aP`5r0KI+7(Ic{y@d zxm07xHe1hB$=)K!Cc*4lQH01&#+q6u7(4h@UuG^cq8H1+V$ZYMGM+$q{85DQlPPV* zAu=Zgl8sHV<2_L2`ZD5r*&KGR6HulHvCF-hnOM8XAzPY-X;tH z!d=4^X}G*pVYvh6suYWJ|FXI46vr{99!6oU2)Z;gf?e>aKHBq~wgjIZAvpS8d@Kmw z=6~~~b8d$e@2!o){0D2OV<83JkXw%%_m-6ZGaN)@kMs;Y^A4v4wXpin>CU#q$NI<8;t|ESD zxSr>=Ww)mVD|i)q7GZZIq<1hFY|oa`l3(#aW&9T+5znvMA`D zBK=b7ML(E+LEBy|PUu1GnP57|Fg54CPX@06qAaRaSXIhF98j&Skq5x^FWO>EfEa%y zm`=r*T4pIE)1qT4TvP4Zu6LoCP_A0?adLA~!C9v+_HE$$Y-FCPQ!oV{Iw%a{9%SjA z_mw#6IAxiPyIv2mpbq`_ff|>ES8Tbgby>J*Nk-gz3dEq&0HdpcBZ~&E|r=&OJ6gx7FepU8CeS~ z)7Olw1(s`TW~;0P#3fj_>tjeklkY5P=&iCASgEf!vKCmSuNhej82Xx#wZPffyZpBM zv60M?G&a3sur?x}sIW!SY7Y}6-9OL+_<*}_+_F0}HcN_Y?q%ly@9)_0E*dGo*wL_1 zw%r9OqAWtF^N6)TG+fawyp_fGXSPXkVXx*H%?QnXBL$lh7~2*KyO7N{XVs@%cUN2| zPKf6!D9_b`vR?>;1ha;)t}?8;lr^;znCC$Te%-wc$QlJQi@QVN^Ae=gKVMuKy9xFU z%8S=<#~w~OuBp9JE|whkmSx%bTBswxoN^94AsrtjY$WDTJ;$_BG66moB#r_zz5$#f z)2mwIzGZ8`yDH5xGJ0051~Mp>>jS%0Kwgk#HnNnb1NVqA*~o>Z&WvNjB3adBq(y*JU`WClfLI6+021N z?t3P47NwXsi(XwkgnW);#VJvMx7hz>y^mC8<`yQDfeBEcT#P7ML?Ke@Sjg67nY`+; zoA%*Wk#8Dq<&m$bz`mFXw!|;Uop%rso+5BQgp}ewRlsfK2R9$vyu)-Yh$_#GI$W~= zbKUL&axvRj(J63^jFPx=F=`@m0p6IndPbdP`Zu0HUcwIxoDGfL0>5y1pi%lDL|-HP zg+L*s>$^+&5krCW9vdM`N!i9_TrQUkguuw-7k=qqGHM*ZGl(txQ7bc{Vy2gwcqBUN) z4jOAWPZV+LSqf*BGV0r*0xY(G-hM!QLuGPY#etRzakPAo6Vaw#nHrG}s&wM69G!qy z^0wAm6Y0bjK{@gwG~fhT>5}uiali%VX>gS|ryi;q^Y^lK2Uy=2oS((CZ~4D0oxTUtv6>JAR5U_6iyz)pa=DfO@w z?wm>&c5-xqguK-UbYUO>|YW1GaZ@K#l41DAZKPE+f=86|Q%4bbz=HX~k9t zTuqLM#AHq00p)vOX8Ch)OzF_f64T>R#0r$F>hQfhl(&Ri%urc}Z*g@v*^HWGSDS<# zd1}K%xq{3#6&Q-bTSnL5c8zva7rXQB0v}bWYy%Y)vzi{+wOWu3w${9?mB(3g6@@3$ zOL1TqrV)43jp{!RYPz^I?Su5)(XI5!L+7ej35?l*{cNoOgO>=tK^Y=s^_Ujbs1}NP z$0%OOGnH-}HoVCy9)zk6(c!$2YgX#GWHI)c$I4ZYt?R(nU)b3S;X+p3X>k>;ym1q% zBG~4H2Wr25vNp^B)ZUy^!WOGs^I#p&4{P-_uuvY`L>YPy%=KEpoO}}kd5dLO$IhVz z!J+#_dhg!9K!&L5mMiEtc|Z>80W#_X0VUG{sTrdlk}*9<4$d6OK@Z6x%&Nb!&#IHJ z^^v3w&b1zp8?;a|=&GGc0Y!(9(aKBl=f8Eu!#$fM=;s^3&0pEM8FC@Py`bb_!YzIQ z)?M#?3aG={dBO@I^Mto4knRqi{-KvAfl1;i9n?Lv@HDiY6dTf=G-1_STgN$TEtUUm z;FcFz8gmsBS&PMIWThJ7ag;uf4BAQuT7EG35ZyLu9TH&afa~%PE zOL!pxgpvtqIkc%O_xAESI z3&(o_<%_#VDK~&)-TduvjVNGAIeiy4AGwm}PKCRchSMA$S2+vaTZ{PhtiEj1rb{HB z();LZMs{7jufAqv*VX&!YesfmHT5+kyRK%nHM3cEUCrrhMs{7z>uW}KT`lNqMs{5t z*Vl~fx?0rNjI0Gp`kIloKv`civKF{mUo)~6n9$dZtOY9Cn%N?2fvUb{WGztB*Nm(M zEPc($TA;448CeV5qOTcQ3)uRak+ncWUo)~6aP&1JYk{V|W@IgJL|ZeVV75tp&B$8d zsJ>=oEik388CeS))7Olw1@5n}8CeTFKwmSm7I>h(W@Ih!AbriqTHwL@nvu1@L$oy$ z3TAt#zGh@C@GyPN$Xejx`kIloz$5fEBWr<2>T5>U0*}(yjI0G7t*;qb3*4%&8CeTF zMqe|s7I>_lX5d6Y2JXdhMSvL+v4W`HP1%J!yWZnqF?&f9rXykV>hP)?7mSk0>|to_ zhFS$i#cZ=^RFE6CU?Yd*8p;VPUX!z}2_HyNy+MS6?J?~-YNp})2?>Ry|8(o6F!w#4 zgR-zAN*nN7(|l2CMy$&!cEKlL7+%US4BJ`^p(jK8W?>MX$T0|E2^xSa4Q#F`wge+O zuEs4~JRk&2#5lMQ+;~QqV(B|5%>xPP!)#>29c06X2sBh*h7k$Eyd z@BQ$N-&sU`E01*CT*gT+;0xq2faEh685P7&;XoXwJc>9qw_43;bx;NSQ#r6h%CEjX zn4udebx*TVvMs9`P>y?}IOSFUTg1CnLu9FvntU1ucJQ8tJ>4xe(p{AScsfTX!fNIg zWDh*ltRY6wlXompaq=Dd1`*$wEEF7+@>B}zeFg{Yu*}C`rwY9x^-_iNXL5iip>MhG zLq{(1R9>LA5y=GNtw|AgSVek!x&U-_`v!bzgV62pmTKmdH2kwT(39Awp-*=Vj`mn( z5T4D^jj-TDYYvhcY^iatII%kf2*@|9$TFWdpzA3&b0^CFIUKOVf}aFCRp+=0zq39( z7kh+{b-PCh^u2firN1Q5f`m$_^AP>x2n0tV!vvl0Ud2C;hglg{J5;ILUL)QY?_lwK zaQD&fxswkTP<#VIlM!#Eh(omHkX8b`b*HXhz{70{>hxV8AIF_~ela3lRU!ZN9GwW0k2R^^MdaF{5Y*ISz9A%k zJ+gn$2}a#e^#%^SVbbGx(+gb4{Zpa)jU4d9bR%s}zrUc<$;Qbbxr(o7yv?`2XQ9tk z;J%3iZkX*j+;q0(?y10kGY9-I+b~Wo9P_PwP6i1Xe5$>Lz9SRhTR4D*d5!?RFzt5A z2zsT$@LM^6%Vq{#a)IFyBl<>w8zdnwXiej zHsqVe;_`z#?$E}}L-Q^UG%=Q9XwuJSVf-GtcVi4c)$JIDB?&<*C|-axggE0?k=3eP z$RXl)IbxFNmCei$v z!+ib-LQ9c;u~2ujPMebmkQKRRYr@B8yns}E^WX@TSY|LXwo{EI|^|Fnu!boa>JSQSYbE9BVW_qWDD+GF| z0`-&7S)cC~YUrHO9_l6OCzf~*RXX!2@c!~{I}=_EkRo~LfJ=B5H;`tHj^n5AY&58- zD)LNtgE3h!edo$gb4X=)-nz8hMCb7SM4p*u7OBpa=JscKSezbZx}8Gqm5L;O4kW*_dy;*I@s~*UB@r19P>-Sr zo?#f0W`Er*+mxTdLH2n>(fj-R&+~AZ3TDO<>Erh#@(UpARjg!DYPp5mFe?Nk|bCINm9(t{R39Y}T z`_`o;gE*Lf(5%`P)W^9Zi9w}+lI!R>&B;1U3-p?DED<+qg)6=cxUcOVt|U?fxBp;4 zh;SWR1GyoL>zie<&|z&`4?l6FxrSB|DJ)R8BIoB_`UyLl$xc=> zm>H85SqQ!bvfkJ|Su>g*UC!ih^H4b>sM=@pcfk9by5~K05=}dkzstjDDu@{iXJjUS z4*nKit`>W`*7erhtfz2+a-=os0k#@ z0wxl1Rwn2mh5WlJV1L0OqhYTLoc2yWP(h76LOZk%Zv3#dnw6%B5EKhrZB4!TC8cGHavfNEtNGlB;v}uBRK#}Z2P@@dQ7?A}2TbqrPV3y4JKYiSh{PDj91~A9xM27Vo7CS?C{ZV^9qkAGvm6Tp|=vLKP~=t;6f- zpTA+}@~#$vK=~hVaWDKH@Lo&QxG$+Ecy(|815u7NsGvIcp7+Cu?y$`w?BrWew-}26 z41r5gv<5XCs0KS6m%NY1=(F%}U*-Q-qkl!VkiP@%yIW|Z-RabH2Df zga3rsf3Mq?!?%)g9@6jAWZpbZ{>8(Iyp+J<6jx=)qh83Bv0cX~;Wpu%eYlw`Dn{9n-i176zWh||Sb?x70d|Klfo zX^e0~CY%bbLIt*n(o01|OmB`rvui<@z^HI=hI>mGODGs4rm+I=|bw~yDJPy!dGt2^7DM?); zEIN;4IlIPQ-opVpEX;|ZXRuI)`>DeGe2z|pneR(to>6r#KbZq_nD|7@T8Ur4(TOne zlu(9rn=J7wT^&v_NObne`uba>B~sGe3poIXSx*9-9(5A#qKdHha&#fg`As;a?3Ogl zmcvxEelN^jsGUP*^dgnaA#0E6>UFqnnG=SYwS?2p2EuYW{>+o}q@(>UOT?S)}@7o_TKzbh)RPv?LglVf-Si1o82%H5HB1_#QR z=*Cf|Z_OheQ=$4yj!uNB#u0jvST)Ri6)`syUBhfZTz(b@>M-@msM9m-M0=`2{!)%^ zgvozyBKe%FVeXSb>*anqM>oRUKM%$LT1Y~2f8HTfpmdK&hGXJHgdKbZ-V)N1 zS%}<)h1Ub=9hv)=uiyY4=06d5y4s2KRYmewa&#iZ{v1r;zF}G8oIBdg;pP*Z!@xX@gF zHV3Nk2BjUU^n|$azPOj0%g^CJ8Io2?V&fhkQN~xp72ERFFyMDxl_DgpNswoFmhtqjt+#y{GQ(N(U$;ZTii`IK4ydbR4O58==V}Z&ew9l50m~*oFESeY6U5u5s%-j)=PAVtYQ_U zL|q0V;G0UWOv~4CAPo~8Mw)(@^n0a3@C6*O!vsS<{9~Fwq=#?7E^>nE6D|>CB5$Wvibm%x~mperFQ% zoJ;3L9GJtzCt}u0{3ec0ghhUrcgxw51SfO#F1vsmhjANGHc;nNSUTO?HoAD5vYKv; z{az$Spldn!$RRxeVsywf3kOjZKdt4PIeHWxmZTod>>9Mify!*y!qEi`%C^?g=kd4( z9g|eh`MxiU7!u)}Xu4554IDLv`#W+bserzi12lM_26Xz>XRC`U(6@3xk4*yq>a!&S zPKKZMOE|E{#61c7jITRe9ab5HOF23c9t1cj_q!=DZ8Muie)P<_!myPSN(L&ZiX<&? zo2!ZY|6IvLDp*q`Qfc{R{Gd;z(}x8Ekl};AVA#kPkZ81w)H>q$L5?EWNFcMOh(c(6 zSB3A(`Jo@ek~=n0m@L4*HbB3MGz}DbUPM`eba*9FvToS56yp+>U%>=f3hda10E)df z(4gK4mqJ2k&VrL?3sJ+!T}&` zl!^mLKT*2VErjeU4rH{Nog1)o14e2)HU|(SpPN&~oCzKn*>W^mBly?R7s5h)R}Pf4 zd`*io-4GJ$mWl-4jiUo$nx7%hXP;;kRB8Bg+efj&MhxGgL13hk}oBVyz zLwc4U=y4-G$KMw{r04m8#z^nL7*XJXC10rl)}rktIFS@;#g;V}A@`JriSk{UHPp4G z;0DB)s0qbG*A^bpN%ywWPc_VO=J0J9(3GedFzP@>oKzcYql^ie_!;F{uF+o z$Bp!-^7lm#=}+SadfZ5VI)7jEkp2vQpvR5$XY%(&59!asHtW5-+pHfU4aGrt0c0&W zOxj5KH0DfIkT+N*6zSO<{Dq@Oqp(tA|BO~ARAT?014iG+&gj5?+ayp54&; zLM^n0aiy24lDeTn>We_?2iZyOAE}^-ZC9+mSPQEWVCljtcSMEPmw?yjb^9N0BxLH5~nCl#vAqlBtAg1BrVB1nK3#UdlmuCW?5mJKF<%yqI4G zmY*B+vD2V{DCR|CE@^`NazOr2GGq+EKk)lMm?;ukLZ%riU$cD@jNnL37j8J#wjs(8lbMiuSoPwe`V+q=1xP>oh8G0vuboi+^YcS z`N@FfMyGonA+9Z;RNg4Fb+jKIZ?6W-k0isCcv}ycoA4_VAktqMc7(UDJp{ZRY$7VD zBi>#^LY)jqZgjfW5#HJYN>5uI?}kU)YeCyblTk{vsZ7+XKi=I$eH}Y*?GdF4oY8)w zz8=hdES0%goTzVLCr-;meIt0gEtR)foTzVN=S{~%eKTnLcq(lw6Se*NHM14nDg=0trLJ8ya> z>bt?*r&F1m#fkbJcH*>5)c1n7&!qA;ixc&I?7ZoisP6}DpG~E0HYe%_*m=`4Q9lUg zK9|beEKbx9u@k3dqJ9{>eSUi0(2=j<_e(ey&cYe>Bka8Cn5Z8GZC_|h+kZe6YMu$> z(OLL=y7!)52S1Uc{oI2HnjLeR`MkIsL$2htMOc!y9V%H3#L(gcq$F{3N z)2Z|lc~K(b7T^Fnx)Zz>-M9^7^F`fboy6YI3Li@$7zVs8gzAXq{FB+VYV{ji*U2E+4}w!`x$ z{KFyquEno`Uz7zBa!T)mpUA4S@+eBR?ATDEVDJ-t@D8xp(=j)m&>{1H9JPQ;OL`4drEnPM96;Sw%wiNyiaz@*4 z+IG`E;Y{g0Ub;UKDIB^A+iy<_rCmf9V)#{d4%=W%-?2se<+1oRu=q{gEOsf0U!M&Y z38N~Dfh>LlEPiWR7W*J^bo3*?f@Sy}^xor-`xBAHMYnUy;y2k@Y=cpi#Xu>33oL$H zH;Y}$;YB7a^@m2UrnzcL}Uyhk}zTs^a5#1WzE(Rde{iD9iPJ3df()W~M8m2#21vCqj z#P={R-wnFlsk>D0-=r7OaUr>x-EsN8{&AsBsf|n8fhyoojLQ!&F5hcAE^~t~w_e1a zMdu4S3mz7-Z`c-Uorx0_ii1$j;Jk(UB}I6FWl=E%`R`B~M76f%5BW(?M4Y}ei*!l})&} zDqZ*~y70rcU5HVQ4#b|N(N7|zh5nxb`>k!U$GJ{;l1Bf`biGW!n)Lr1UAVn%7szlH z{fRvb(?92Ocw;W!S;KKrh9IM)JGH14)xo{g|DQI}du3rGikJAn( ziS-k0uN4dRueg`Jwu6_dfon#KXUO zehYd1Nn3gKWpvuV5qZtbL+~MmbyTb(CN0!vty#@tR|fe+DxPXNXI9)p4!$#KS$Q06 z=&NzmiLDB;{T)#MlmYd&Rue$o>xOn{#Ql5V{uu-A7HndBp*TQkg#8D=eqy(s*oY$+KH6d}ydWS18CCt;bn%KeYW|5wP&A35e~NHbF%;Q=6;s#!KF zw>GJ?;<~+_H$iQUW8927y6fF++(^i68b#YYg3{Q~-~~GjJ&xIXBB0|2s^{n3@*jgf zG%C(ajY19)!%Z7=^_)Xgkfnw#=A)3f8(#D^Mcp@GyiWUs#-w!;_I6T8$}^V3HQ3~R?RWXHB^U02X>id)ROVP<{k_S zjYgrCE2xnE7d`SaHdU?DcE&((HdgZUhQ$j~j&bY_@4G6n@8E$g4=Y&jS$)~o&0B8C zJZR39?oGj8d724Enk0fW8EQ)-n>{hJZm)w;CSM#oj~DdNJ?w}KWesG!Z>ZM*#T7=+ zuR<~@cXURt!+S&ld;S44pRW`koZ<$zjAPl9)x_p>(%UEeorA4VL>jRbG`lu62+alP zp`n<(uEUi=Gx(&tW9&w)N&(eR%}G>OABOvs_fAE4|H*+YlzWB*P|8M{gPj5`1 zMWamzYhT_tHdVB$2sOnFhLNRWPGP%RwQ3Dor(J#bUnq`Wcdt0+YzsilS&x{*UCkQo zmgFVUsAD4mzYw50(5%ASYTW-wg(rmC_WY)Mm;p$<08fjfFt12J>GFiw-k#s;2XJ{i z02CeD^E>?jE^7+_OIpwG^#i!HEdW@zdj6mvz$I+~%msiy>Ibm3EdZEQdj6yzz{PC= z^Z>x0^#ee1qL{J}({ROn0Qif30Grzaz*fKK4*dX<)TPS+j+#A>>>j|JtN$1%!(Ob& zfhL42)eZ}ZDhfwUDzaoulARuI;|+7FfislY%{y4+jJ4O|5Y;fmKis%3KtyMuwm)=! zTCos4_-pX}jxRUmE%s_`%?`UJmqy*fp()clhQIVvi+i*u!V}PXIhNzlJ|Yu`mY&N* z?ux=HxdM(OJ%8h67E^KI&SDW_P;N{(nKN*#6FW5Vp0;fHg6Wi0K=$)<908e{iwj0i zze_5}A*wxpXQg+}P8>lYsWzw`ya90=bXJB0+Y>6d2H6>vaji)`S%?m(0LDc?&p%j+ zJQ-IjCy&)lr&%$b%raj}8}5^h8#fA3wXCX&Hdw^YYZ&Ib889n)q{1zN{d)e%z-?=N znEwE%(_Quy`R>$lb(LAU&$1@S&@)lM%0M;@tG1!xl#xg(F@mVQz7<(>x&;e;I}O-W zWni?rrouCv^LqZ(E%eaIxF0e+e{7d|8;2>jT&t3dC-wPgnvVl`u;Rvw4x zVIw8CGVS_$>=o*TTHegUf+jv&FT{Er`^W;eltpEUK9imNB>Weps_9UN!voMxcwFg$ zmv7I183y3$|NQl%PrBDySb<-<92Zcd&@D(%?|mnspV}OPP32gPnS}ZbW*j(g}E0_xz88``{pz&tMa>y;8ts7&yORxMmc_$0^Dd zmt$BMD?-|HC94j-Q^XUW{V?k3r|fbr>f4}B-;;P9Q~?d+RZrM3Po7HtFCFCk*ZyDm znV3S++X-y>=kKn-r5M+Hux-|HH%42$qIl%mHdi2$XnrO%!m!|E{9kIyf9?O3pCvYn z%iPpvk<@PfoKUmm3giD$6aH)euly{r*?-NTSp&_U6lxaMnvDNTJ@jAuf8}RM&Ca=F z2F;$0X6J^Qg^dQhyT8k*`>*}K z^0TD2Pq|fU8^%LpEeGuL1yV0x>7PuBdiJw2#wCl7vT2HpkO6xpp8b?~+ z3$Jl)$5tE!E&%EWc8^+a1gK?!7!r;HQ6abx?LMH}cEeZj*v821reA#qQtJukdSH5R zx0qxmG8x#Y7ag#i&@_P4YM2u8o1g)9JRBQ<<3Zixh;fsEBRz~Qirqsf66AD?Fw7LB zW^pheMzws@L9i7J3UFO;UlWJba%Ez@k+Ta(MN83FQK`C!W1AVnH86^1r+gev#_(NX znou-iYzdbOh@g2SZQ=nFQ3wmRmuoc>x8Y9av@t3UnDgEvc@7kxir?mn%{&NY!Z3{U zN9<;$fU&z!@N{z%oJ@(Z9>>FXl?Modz5X_3+P;#iEDgVyx5)*(E%n=IjFno6cB5j|+xe0OJ)C&beU9gRxZ$A%pG+8I5M$E>tm6 zXGV$fKJidoq6Nj0okz-WJ$9FCSh7Gqd>#+e#a+&XU?V|#0|e3Cg7+oEvSAZLO&u^fH6c4x{WV>dW{soGR`B%eg5`=K@@}Hha2;tfoT4K!8oM zh2T@y|8Cr@(YKM0->S6)=W?tek6@SI0dgd7)G2|`+N}t}SuteqgM!A@2Lmbzr^nhmzpMQb)7#gJVXrz?Fr z*p1swO43?B>Ssd1O+U;Uv@sL`A~+Xgf2v}cx<(7b{lhht^P(8(AR?fdiU%{q;;V$!alZJt2`0UuFRITLn0xO)^!j+fD3#h4Zl@SU>F zTnT9QG$Dp$*33gFy^bH^Zxa_bi-#LU)2@|q>0#ogM&vWO3;K=}+oS(1|=)c_<0oWqVj}fX%G8d#R9@9t_fAQawlD24Y+A)XQN_ zbTXHc9il{L8L)*bzDZ1)^@i2VmCiQyQ@|{(I|PnypK1*R@Q^)1Wm?_H!~0D7vYnPv z>Ig2(j!YS*h_bX{%iYM-m%}`LktYxCHI|H3Ebo4=ioEQw@M2)r9h$T-4r1~+cv%!W z-6*ZFN{-0D925tyW;LpB$M5Xj0|l)b}M66v54IYHXXP$yZV4#I8wHn4akM* z-U{b%hgP4$1)4cdMk?{ejfQa@w)Mgoi*UF+eSkrj@`U>y{3OoB@;ZjC9nmXPiU{y|x)8Jx#=*Bh|;wJs$^wtnGhi?w44R>4BviRvp)zjZJ0PpTy$=)I zt8WixGT2sRDcnwrNly*Bd4mN2s-zExeRhj z-IF6XjJEm@nv=NLZj{_}%-M&nCY;aQeI7ZAVrxvMr<|)jgNx>yd03FQ1JrR(np${_ z(Q3829(KJdHoy%mwbFi|K{YV~LedQr6Fit@0x@!f?%LLZe8vtOWU<%72@oqAB9GkF z&D$MUctFU+(E>Y)GF200oRn>YN`)Y{Y>yyLL>*HK+`SaEK&Zss_|*!uRka|VcMykX zxcLu@Q?$x3)^Mor3-EVrUz)S6mGzJq+=V%iwI(ZMY=Z}iley>~R&xzTkDMB@Z61f` z2c0P3^?<_=tTB|q2c}wV<@0o}r?P#p(1m`EF2I4=`#~2L?I<6C(2>E!gxLySPMP!e zQV+ zqf+dnAhrR?Kx#T3&Kux-Fqw07EU@kn zu;gxpuuk(P>X%Ma6fOYp<8oBg8S!h>eo5w0kJ0`&P4z)-5(aeLfu9Xp2_$r=7=@?*Nrd)@#5(NBtD5m&P zz_>ZsLP2iFfVCULN$t~Hujym_P2>16)N0y`_CR6{&!e_cFXYN_02Cj1DDKaKBIL>5 zL$*oWHRsVTjH1FYlyALPS*{+yj}AJg4=(eZM{i zC?uki#&g93IZ%gfAu-hHk=s%yRVaTD2lz}#AOOC1e~r9NDF-Dn4!_gjJqP0t<^UaI zJOnzG@EWdy{64A>{tyoAK|)xeok2JPBAk*9=WWx*UL0GQVfu@U0a`_nxKDhB+bq<@ z3D)$a17mO`5LUj2a$pU~25ldsSkuGeL!DFUz{5B?5F>lxt}?D=?Pd+j_3Hh#;7GpJ z$eIllS-~ELbO==ML3}5LaesXs5R5;Z18|IS8E|?KR?xfYnGOSg1PAaK)4hk`ePDA_ zoe@+&k^^y!>JZ}elrgRT&5Udp`lC3Y$H-nV4Brc8sdn)`ngef)Y6)+uP}dB!a@S(lrXhdfv-g(C%cIZs?S%+ggSEjlt`VP$)bT2eKZye|#C;mX z>H4MBKNaXt=71g+_Ge-*>s{FRd=KtJCKGHcfGe>nS*Gbm_r-{a^Fd9(|8xYT0FpE5PKnrr>;L07InWN>S)Vk|FNC)+skwjrq+&>GSM|k^A+Q^RL4coJMfVGMY z3D>0lG3q>wrgoj&MVEWRBPekPt43bE733aM(FV`q0V_)xw83K1{S6v6jH!SK5j#Y8 zx-MRN;eBe8pE#wXb4T^LdhqPe14kIod3&kK4v@hAk-X!?4Yar`g`E+wg@OEP7uEB4 zP|3FnU}<11jOg@T2n3=#In(fFqH0LP^LcPaN7EHOD(Aex-Jv*{Q@rRFSI3J{^+xao zJQ(I|YoQ>C+K<+YA}*CK0drJ5@N`^!!;JuMaV1W%54h@Z&Q@8>UkHsj&a35tb-^A$ zA}nnox>EHb9;RA62}RLIt#6n>CMn9p(1s%yz>Bp27}#eXn-aq^0}fuI1w#MfGG=s| zptvNwR11Qh9nESL8~cG_hys+12#xVvYhI?M#rfOegh2UiH|(PPs@}~c0&=SGQEqKD zkOhdmLy&XankW}+57Ns|JV>uN@gTkO#Dnyz6A#j>v2|X=t5k8J-G7r+&6Z(oDjl|mZ4*CKIgczFIDAx0Smiu-1>|?aEW8DAwg^^)|BK27={4xhV)ot) zdA)JqO^N70?7&y7OB`#K;#g&pyLhuBSI>10mz0yPjxMX3!@mI@(CL90o( zY{AMZjJB@_%2R{yvm6N}xki>nI*9ciqANg3@}&ap4cN!rhZh?5G4u9f@jzb6y;v>^ zSVj=UUO>F1zqC-6M|TN*qZTDJnLiFx;{6z03<_i{=Z*ule_(`TX%~{{6>S^geKW zoByEB=8-pr;`mWAI%`$D4aSFNyTK37c7q>*JYTO%o|7p3C>lI4n+<+U3#F*%ed*>a zTJ^kF+{V*P#8iuXCzT_@I}f>=DD-VL#RKILpyD4w=?idyq9|R@4NheP|8XAF!GQ%Y zuz@FXwrrNj`2PAap2tybDSvqW z^Hcws*`glFvOVe@c(ivf`kw{=hwl9Re~yX&NPO4<%B%;?J8qjra_2?n=V5w7uSwn! zHy}P2F4iKW9?I$=rmO+4aA7S(>`Opyx)td27>u#t`yE?>BI`^44n#pBePNEu(NQT7 z#5mM)W$FSI;y`*S++T_hRj_`6hjCwB(qCFt5aLGn;;SfL046_L@~Q$pMWE76IfC^N ztM1x{z6ib!cFR}T#RKIkd<^NeN(ERC%a?fgh>YEmLxpM`*4j!LMrc&e5sB$m4AWm> z&n0WL_mzF_S5(IE%UUoanWHORRNTeMY=g$4FfiBP+<p`_h)c>}wV+l@5Ic0&(|l1p% z1}MK#e~)hLu19nWhNNS#-&4-9LM>Mk9xf_F_e~7l>EVvj4(bN&BZUx zP5&9a7eA3GV=DT##1p8pD^P1UZ$ftcQ?YtutBPE1s3BU!x`#pZ^!K-b;tX~aiz6tm z00=7U5gc6NK)wwiXKDv>c{-5q0LWR|fn1gjsK$d9-a!ERn?*qtk z?LerONQ$uj06{ElJ)R4FVS^vmU6(z4cE$1)Z;<3sS@MElXBVock%|7El(R>-7 zLeRHoH*?lPXQpz1s zHf=;?1)+QnIEUhVVAE%an4!#jINR0X6Nn7#$T0(Z1@g2butb=ryh9%?GM@7I9Y3>e zAYYuO;*3~7J&eBxtvj^SN*gyeTK_OBv=U@xS}$Jb(fUWwx>q}`v<+pW^-r@xD?wJK zb&F5ypF!(2+G(W?H5;venH5?IvNElkeOm9pImKwv3dH}R-%KX6+WU)_i9f6Tr()Z^ zZIS=B@7?gG-fdE#A^eQ1R4C~(7?|8FRAhItkwX&N)cw%4l%X`GSwniWs%evjzk_>| zzd}5AxBB6OQTaPLMEAGT^&>shQ|`CQ*8FcAq7v?`cR_#ICh3Cy?-=b?08_^&0NFT> z+meA1OGGgGp3p1zl9P6Af*j?kmIGP-5n~QFU$`DC`9%K%qdLkksuDB8m>&+= zRu04H(`amPVCgn&^BAtN5L02ppg^B{`vB6m#k}Ogn8r3SIJ4uHs%b`>}!qZe{%>N*P z``Fba=xSI-)ySK_`AHWsLlyLw>$&h$>1hu4a>p4QJ&HizNzSj^FU?r}N=AX|#H1qaY z7AnfDI)s~F$Pb+NkhlqJ9EC9pK|)j*Dn+>v=5mk`8yFA5`Kc#Bg6bi`z1jRV{A3{4 zJ8p0KVaWIYiZ&UdC_?&v+r{hM<#z$m8E&C`~)>Mv1QM(ZiC9EuAI$ zp2yOBu(Xz)CG3+Xg}uPb&Uiky#pXCwLL%9rX^rIm7XHv?m>gY-B*Y%1leLf&g*20` zkkkhc8+;z;uVbfc{F7{0S*jvS;W;SFUgEU_#5fb&9joV|N6`F)rO>Vr=9VP>r| zg&e9Q?dKTZ*do%8B)MeZa|))hm4Z#zP<9yMNm+4o=1|2CBi9sF^zbcjd zP5K#AM(-gT< z+fGrz#3W6ovH+aM0XufePlUZSbqS4kR;4Sab95ypsqxW_(3DV#trjfcPY@CN@3 zj;3Q`88n@~`EWscXwKw76C&(K?suPkSg9FvS zyanshbW0-L8FpPQDyG(grWKdOb1_+&GwO7#&dvjtu5%E@meoQ^yRg z0hX>aVX-r`7O0rBjdje>IxVxSTN%251654jgiy_R({LdN7Iqn0&w*<8m&6SmuuqI7 zaU(}pVlo=6OVcfh7jZNl6Um@y#U*hQ)Yv`Xp2W-AN{SGMG|EQpAAyZ&gn6 zn|X+u23qO@Su=u;s36}0QaA9Dikg1C+XMTvlR0bHkZsbqbEnG?*dy_GyB=2)#`eqL>KiUJFXehyHL2T0IGB?aTgA}u^m(hZ|a(c z+z@{6R21A*5Rpr|7ZG{SglRW#q(J__r^B5IX;a|lJ5DK$-kQ zcvsbem+mS;-B4lkZh(6!JDa@(!(|daiLJuRlnDziNVu*LNof$4uLSmL9;uBCeF$v2 z)V8{&!t*xpd>K2>1H%mj`_#&C8HAWRnR5@=R@OveE#c>d;78og(I>Rh`CnB*J*ch7rd?>@NOMn;LMS%WKyAijIOv7LwNG@z+m8{w|0aJ>Uu zU%}4xzz*cMLfjkN^x;i}J7`HoyU$cG?$kna6l1!xTBuVh9oPkuujD1U2T}8*D2P}Q z6^T%TkD}(0j0ExXMx#nqX5b-}wF{FfICt|CorE)8p2Iy;>B1gReHUJ;v6T00r&vuy zc}jAeqem*x_VTkUK}+|i7JX0wd=0p~ik-{B?WMvvba1{PWLLx-jN2%8F|}T}_#vTG zo{D)Y?%}T0!fp&~y6lF#r^5F>@O{_r`Ifgz;&uQw3MXqf8zm>RmK@CU<$9ge1N_Ue z$EefCt|M{1UYT+~@SGe*c}SfOQTpcXK!D#vbsc!RTlYN8!T!$w0Z)Ag-K>UgP|i8S z5YQZ@G%VDtX0c>UlGjcRffFdr4lW&)$g`FEIe5fTQ_3cZLp>_`A`2s&c2OD$+W$Io zGo`>doP$JIB!*HU=w#&q4v;usP6skQ8;R5>mAP|02lyDnr$}gwwWy+4%OgIvYo-r! zfQ>Po3^p}s2?{+2Qsnnhh50d#&cv8sK&bK}zi8KVAL2k5qgz6FLE2$Ra7?{XA@&9i z*fC-k`NaBT%^=S=av+TnoPabv-F38gDuf^A=mrS)7Zv=4mR?AZkid!xp>{3Gcjtfz zrrSfja7MB$`lG__Jve~JxLq7@%P7q^VSBN#+wDb+<0Po*i56o0Q{nucS~x$MFo}Ah zT}$)5H~_|&ZUJ03BUwgYR9L+?#G_@lXSw&p?sNVK!t`?3p`V6~yWp{S#)H$Ta6oD9MF;8F@ogv1TGH(p^uLsQ&*I5=2&DxKE|DL zE2h>iq&uWzx0?)n{jhjJ0CA0oivoOtg1osBjC(okB1Afyard97Ou77>8GHc}($63v zt+VMYGCvOReGK4Rn>GP{xQIAdI7%sq4YF*|KT#2zB9qu07x1M!Gx#zS_^`|mAO@`f z&F8C|4Kq#i{E4D_5M{)E)H`9N#S`I6hkozM);H zR)V1N))-$gQO2ubsL5v7&OH65ol@K?@s;3uVjs=Qzv z`Ty0O$^T#7Im*^XRIK7l$k)mRB?kn;Hts+tAZur?bhWLug&SQ!T z>Zv}`%xbbo)Xa&3QG(|POlV^{6Hb5GreoBQ4x&*hSEp32Ymae2gz5$-&;2(ly?6k6!JA0_q+1YIa+7IW4WoLqRjHM6t&g}8_sOXH#Pfl8AkXMa z6ecyE?h_Y6>tTBkhzX~HClT|1r6xrUE@|z^^!Oh}i?1xjJRe3*XV@2A|BO?HjS`&f zaYx*wQj{W7YG7hX_oaU@AchM}`0!e@!N=nLCBpGCF!)Cyz7rDYLPU;2#8a6^G0EXD zCc(rb6#;t)K!)o&q=NLYw&igNl#mOuYUY3sj+qiZdZq&Kp#T_8lbr;R23Q_s!Z_ap znu)R~4RjsWPo(P$Aw=SDQ_1P>VMqXbM<5u%;Q7tZUi3 zlVlsuk<}K8JzvfX#SZlX=OML&@54D}cZ7AI_LA6;-CLl(%VFSJ?>hH;DT#7$C4#>d z@cZfZyl)E+u^Mtjp*|&ar;3<70%P!d_A%(+ZDrx*YBVO0(GW3>Vgzc1LLR!@&2L+` zDln6)Abli|{(%=M5^N8Rkkc5T2E?yI4oZ>BSU?rs$a>$Q4^$vO3XuQE3mF0)A$O%9 zS3t}b=tijF#^IroZlPIK*|j_xaR0;(ci{-;oMD%}-+5ss3c?CW5VF*V zPQ~Cc{2BvdAoI#61CIr{|KKH;upTt4tjgeV{FEljz;a0jPzk(a5qLak{i{}5`9$Ce z{M?e(x&dMBmW{X*C{rd)@ICL?E8Zk2;BAK9P8uPQW;yA(pjPWYQ2%hxq=2 zSA1tE64a4SHw@1J>YwwXZiz&TVLp*~CIJ7E9q_`#R%M)1K|G6JaucON-Z@VCedt6t zJR7|JMk}v;GVmN={WUw*1tYu);kiKhJ6@EDA|P)ZyHN$t1K!{A;)N=ZSmhId=kw1l zf_jj5lkft5l&J#H&Gz7h{CLBx@(I9;v_M@nVrF5n^Mcm-4i7KpN1G@J>!jTxa!KG; ziTsMC{&cc1yo4V-q#}W8KIwQVEXyaeYqEv=i!jzPT9;l1m@{c(oSw)q7LGAd?uph976D7#s^a(TNzmmLD;Rfy^wQ7`#po*wKJNCNX$DKh9J! zs0N+rL=4`*kC?*ger7`%xeXQCL$H;`3JbfVL}!JGLZyK>N? zmQN1eg0t>_*q5-u9cIz2!HuiLxlMd4Ke36jAlsT-9qCjY-Uf#Mt)F2&k$5{l&5&Pm zON6gq1DSTXuA<%nX8*;@Y@$4XTL0K!G(;~tm4$cC0=0aC@GkKCPhNgW5N<-%+Om7} zVp=5LjeYn@yg(BLL0%+|wR+LXX5l@6Jck!@j9WfQcrVaq*wGFg#PaZx1lK;mp4@09C(mc4gyQE>6B;teowUpIGQH5hTSg1s?&uU*e@VQ3~Ms z8f+FQXiMrzCz|1-v&1otPNX6h@B=hqeKlt1lZ{ue-O(<#xr6p`BxlV-Q zv-r)G&`KB@RL zi2fQc(U6MX-6cx;%(#L03_r7p0s&@&rK1>hq7w!2Sup!`z0C57!RPqtB{4W;Bfli4 z7 zS2~r6uYu&x%mT@LV)1o;rb#Sz9<3Mba+R{1Gj1Ec0eU~nOK-YZ6vAEUWYzfQERoD7 z7T*HXpW|g3V$pZd%u@PzMv?e7KedS>AsedgRxdh{hVOve&uiwEPZGY%PcS55V8m=7 zQD9?&Qx?7lV!yykY@#g4hU%=`k4~iF`?JC?pFI2kB!7{YWXQvk9fbzs!i`G#7-joq zR1iPp=QmL(&TnlcP}d{cnNB3*c7EjTnC26YA3;2R$Sxj>x1-u6Lhjka@qf(EaiVlc z8%<<3Z}p|qjpk24^ABc)Ws-5ApJ2f(nP5UEhK(7 zIu(hZ^NTXnMTA~HY4`;=zMYq2f_r1L;xutb#Av<#B|ou=(jXfrj(FYZR2Y5*c7LLq zT|Qy>H9y4=hQ4de+yt_6G43XQ!w)o37Ubi`&8=Q^vW@sHKjav-h za|bdVRH{zK7=`N`a-bEA@j?L-Vwx~wITjKXxo%YP{5UI<5;;vS$YfUj9+3C4LtZcf zXNLxc-pSk*TT8=#YoJUr3S^_~DGKLQ1E0b#5J{rY8>LiM ze}LBb8ADH6`M~Ggfs5u5%LO}5S{Xhlrw$uO3aAoTL?TYgSmYpqRh;l7zB7XGKXOPk z@)D-NOfSS3?wCp!{sgAK#mY40CtSRNYBr_z}}bBsrLxY3!R;fgU)GHoO|8?PWGxRW6-V$`{+ z!5!WjM{y0SnS+3fe!)lw&k!Zlf6;=vXZ2-QY`OTB%!B4!>E?~TLtisnrDp!BubE4v zX8xwHnMVk}&IEyJ8Nm2?$R+_hVGs5D*z_Km6a@#MZLY&uqD zIN-~QA(NR0>JO0d0JZT7HA-H)eweO>bJ&Fxu`@c@#4c*gp}iDxJQt2Swh3ci-OQ?% zJX$q}1AM4}UIO@Z?&Z#^aDNg9{22FhN31LvoGiK{M1lz`d$!m@t(wcz+?;J^`fbkr zC5|PBP`k}4HxNPPCU##sjM;?C2g>nHQ%gMFKxZixZsu`-rKIW8gH1o2guAEGg&vMB zgabZS!hLM7jq2tgmGY*afh78{)8&y^n;y`7D&u1nl=FE|`XWJpQA>Almt8Q#Ll$jFw` z4q1BTa=#ZUg!Xg54y&lSWT|S}@K|b1{?()ARJPEvw~4B`NN10N8d&}f2Odr;s0TPu zhov|Pb^6*6?x#u@206MACZ5&?vTmYqDpIREnRVE7mCD5ubZ#9>w3&sW)3mFiutsB| z0HYRc`c!d41@{m?Zdeos_7v=@SregY7A&{W4nox z%a)SP4fG7Hx7WGFoAh6cDrxZq^jfx?D#136?p@2zo*IT#G@ zS)&j(q@5OOMhmeAYY7Le5HDm&?RRHLqXZ|$x|8YIZ`B*#ZuAt62E#nW8%$q{WPlzO zr*a?*&&@5U8@}DdtizsdUJYK~fefY^nO2>~14kYkGU6RB;l{%-aoK^hu_~t1c`(Ua z9&8%@DuciTVNu0&1`j5gCtzB*M-;EgT1P!Z?pf+gp2i{sEOaj(T8$})$yr(u^||vS zUpDP2^&we$0%BTr0%BTz0%BTm0%BUpgDEn_=kJ4Bri(G5aoqX6il-&%ZZy+_HDG70 zD6Vc*b(_HiN9s9r+x)Q{MRBRm>$CacfStYrSQv3BiSjCNEl{QC9N3UQ6zEHqI}Tu4 zv=23OjCu^1DeXYzFd9m0Cv#VX7d6NY!a$U zeFGCBA5SJjh)%xtmJS)($b+W^CzCSMFM^!BmqAXp1x@nI^i4cOFwgYOVCxeMY;n%? zE$mD&%=C*v)F&B;;+yGPnP>{+yvvz>2^hMKgQ3v<;{uAZK&)E_s_;@Kg2ES~T>!ca z41Jt|q1I$ucpEMDU`SL6cJB>USB%SKxcLdVp;=tEl!4UxN+?bAy)W2WE;+!&L+U99%P5SK@=Y9Rm7Fn4YOV^A+%0qC{Gr^uGa!=$Ol%a7HqSkWb0oha2?cw ztKY|EH?v9(2b%?`F)g41vfu^*r$!~Rt`N8mX~DI`C(ErL*>GwbRpe`jfOdlxXu&vI zwJG0oCOCRc=ks(}fVzf_s-MdLkbBhm}qwAO#PVAic#Bi`P9w6wQm zrwI6(Y}e|2d0Le{2`~+N4jaO*am*t(%^QCPmp4!{-ff)+qSyUUe4 zBC0yaHLeBMg0YfCX+4y7B`#4#Er9w+4`2s}lc>L%&1k3OVk&9D)K8cukwVh8vPx$b z7gSjfD4_~EX6j}pP!X*uKy^;lgcejoqh`rMq01>_O@~8R(SyrV3Z3&+)q-natYjjC z5^kPhWba^yP}72HG0nn8cgQRzWZ_lW(^(YY-@$@YXTlVoiT$2>=n&K`OrXLVZIj9Y|K?~XQIBvYHC5$ zDi5R3Lr{}EprnBlcAqiaIOnY@GEy|As#z=Sy?sjVa(^u?_WPv9+IxVO_WC?ZW6eEKPjh=gBLz4k zXG_R5`5-OL35AAW57S@5-JyH1md0FN6>se!T3S10J8nZu8*;Ft*=vxCG1ke4YH4;b zSW@FHK1@rCL;H|B0clEb!y0e$;aZyPA2n@}OCUz-Bj8~5?%?do5`qQ0l)t3J`}asK z{TuX5g~MBl?cqR>2IQYN+dcs;!w zD>W%>7A8I<%0>3u(2Dg0Ep0B|QOIFacVx{G{@5;}Uv^3az z7<}eRLb1Cpb#7}<*3w$f2*Me|)Bh=Y8rpBeA{CO7r)p_v!1YLkDaUCBt{^UjPt(%i zpyzOy)Z){%wCLT{BsKU9Ee#HiHBoR3TT^5ZLQ=obC_YnDi^8uo;Z=VS!STlX=(G$m4j%V7v~qkzOdVQQV?71 zlatW3DaQ7@E&*W7zlPD7pS5`Y&;0c_lx)qE=f zcQFl|FX^huO}auBbY4$)=Q~yssGUV{2@xsp>5>)o>_e8M0HaH#Fls{~%T-t_88|Hv zBzBn;V(oUnPo~+)xFEC3yT7 z<3luV`5&UDfc=rz zKkt@;SigV)p~E}nJM3`NJyO707aD!}H`t5qyH^fmw}vPA*jb%@RnXJr?~?-AxG;x` z2btky+)WX+MZI4R+zM98wuHkxAO#L(!BZ)e_ji+pZQCA{0ycS`j%%l$HhV}4P}|;G z3fGibi~vXVNngV~ECqAQ3S)4~)k$w)@DV9c_472GK&Fuy6!4-?Omq0&@G{oCCPLUJUmf;Laz9Ywuyla9TEtyU6S{G+zh&8$iz^ z2W^^L4)5<$TCv?pXw2eSx?&@rhmjYP8wpS}BuX{+!V0Pgr`9 zd`q|WfW83G3&}xi_riGNTfe+0MbqXLrmkh!bEgTUJa|ld38q~_Zdw!D*rHBiB*;icpjVx4AY`waiq?^QYG&1L&&ys3yh_yPBQCVTDl z8qB+l+`Lv6(862eN9!fV@zt}FDEy1tjY0BDONV_xf)4oEI|gj;(Q3^ zEf{zOxq*C8i1S7ZwSW9;@D!VpZF`93DJD zxCgOkJn6??jONOo{0kOdMQ-6F4BK9u_oUcXzYlv(BkKqIH;lTP+$bwlUWA3Z!;`Ni z$7)=eOr?yGV$(hV+%@EI$X^`B95r>GDA zPYPU{l>m&4QLVoOCWHJx`hng$Hz=|&8j*rlyFfcy?=$$Oi`Ga%YRQj>EHR!HKIdFB zIzbMioxa^)3UQ(o#AYiQwj|&rDS)lr><9*LliG0`PvUlTvJ^-seAbJ@(2@-3ULafCi7QnMQh-h(n zbS#Z-xslO_M4s`EPNgBl@nJ(1qP+e6Mm*`gd>mav4p?_8r?Xo+-Z^beIbglHbU$Jv z-t&j(S~Ot3OBVGwy-E%sUBj`gis6238b-c`oyz1Yc)~W(^`&6u!!{_KEltY;9G9n&;?$WD zqqVIRTFvuJZ2-~HKI}N$L9YtG+D;0qmY#&(5250E3e`QF@uLb_+tbkUc~)CD8_$r8 zlVDjL$*iYBPILzvOrPI$SoetslX%z<5ni!NjL{v5sCjRETBY0rGTTWCvkq&n3rKBe z8frctvRBf=s^MRCMIuWaJ@v`1hj~+tA%j@c`Jt%0+YOkmC0UhGp6|xn~s!%)xi$qSsDdO zETCq75$d26R9u88iI$Rr)@YSeV$W6iBps3hSKrN^a4P~!(}4LFI<=N<&Qw!H|2Amo z_*@qikZ>g`hTFZvhWBmG=&%&P`uQo8&WQRmM~{*MR=eDYrF|o}DFrE?BNO*$1ZjWFbK0rU6|D|M6A4i zjw)FR12r2_gG`v&w`iDg#MpYPzS}fLa8okpsTdnQRtng}VNyK`VI}8 zZ+vadCz1ByHU*d9QN`eT91$v~FIogf9P&FK*(?K@ElW{XvGv7cRSN@>0;=HHis*lJ%fFAezv zDUeRaOli!Mq+r@B*5dMD(UYaX6;`YzAWxA3>Ex}H0Q{j8K%|qUbcPn~H)1(&?>|)v zVl#4sQMcAC0r(>+fK9zIbCj)Wi#De}mV(!4-E`1nu+e)1?=&fJ&3kIBwsNWj;OTMz ztzzaS0e>O|&`xmG%bNL9iKuZ_xV@yUKa+x5sJUp_>j@YCTr#3%s|WEHQV=IENEv<3 zT?Kog@k=Q{ZA;BG;zWo|;a!0u%wI{tY+Rf}u&4yw8B*X{doX^ZtZPXNe=P;FZLT?l z%7IqRo?#ST^|s`1q+mALXBr0X1G&e#w&(m-4xW8HPy+9FQt+IszggCrF}%0x>4v|T zf@x=xmqt8O3SwbF)md!hAEZDw^_ZhbJhrNIJ2%N}$2m(1UK`H1^CM2CxO4nb4yI!x zG7N@`idOtd4(8kx?&u6z1<(-&^PLUc zqIC>_Y)D_R{^+?<(CkfB3App5z!k=T5|HOhfo#q{MO*^#0x5v4?(^T+qMFt%dEb46 zTa;kV@o0Oi63lHqn71jx+|Gk}yA;f(708@oHIVTPk+<&G9n$fq(ACk3nt5k@0tvK!;RyVUnf!E0WY z$_=rxv>5OKDS%DAiKL#2NAd2MTi?i@6d#m>*NsPhT+uw5<21NDd$sK$B8H1iVEForyEkCMZ6w!yM)JzfO$7&(-7J`(g7V2ze3 zHtcaJXpKF_P#+4W6&d#za=2a2KtnC`SKfa@5zdq3IIOXX?RiQO&R@xK?pl_O)b=EB zJX+vFQc09L^zN(vP7M~>aB()`zGR1x4sO^% zTK<`r=cQmaJFn-(Ov6`F;vZ4~TO2^EB#$@g=bzMw6CXdFE0)cd|^6^gwZQeOWe=f&3H z1t0Uh(OQLK?}e7xzsZ@g6-G9w%W09Y604ymW{0=~ zKFB$3LO5wnq^1?&V>bvUi#-=QZBj^iO(y5XRt|1F)%Tj|_}8S3v^E!GA6gn|;F_yC*9mwHbxBNk&3pHh| zU(ZVNPHUr~#^IcN91aq!663g4tEJ+&TZ5NiJ*h_1+G$#eLuDmkCZ%&JPXflY4p_I? zRYO>K4re^pO;uzaDtXr~QEuIuimY1;*6pL*y0sNqw+^gZs@%GD6 zisP|v22fX$YFgU5nZVhf6lXlvb&{i&wr&==isP|vHc$^F)wHyAn*rwlQk?Nvw>dd# zY3sHiS8+VnZK-HR*b3Giq`d9EL5^Cw?Y0IE!5)Dxnc9H;9YIOzY1_~=uncN*pc=ux zk>9XGwP4k}Y*j1t=^& zZAU3O1!1%kG$Pnr`7{bv>U`SHQe+B9Y8NO)u*dc(6|nyLv|Xi06_nL((28K!=Nr!n zs=`m(ok%b5piaT;o6w9P4&d{zz{S?5?LnlNcWe*NtBZ)2w zwE-M+_a$+I+PThLg=Z}ZC98d;uxjZ^=>71jxGwhO^0cMlq_s>6tq%75Pjt|(zhc)R ztA60xH@wuA6H)W79opIR5CKK&4K1@3QkdBZ4lqnZFMysSSYU9=nf>MoymDp-2#M)P|X)wPq6_ji=w9Jl{!;D`+2x@jrXqkPB zh?#dzEUX{|1v@sJw7xBcR#X0MIzg?z6Gl?UNg>t1Vi7vdG4*)$L5S~$kJ9l(l)P() zDJ=RRB+>Um#^(egKHkx(IsdS)s7fb>lGXQ#Sb2S^&RQ`DInxiqM(89dgj#HGvH}c3 zI-ML&TBk^%Rkz)i!$w9Z3FU`jBXp`1Laq7LfVfaU3MsE26Y=tn=JtJwlo1=Oeo1gz z=opt`+b)w{4>e+(zBKS`lg*PAf( zXrHmV`a%BBVIy?56hir#P#e)rfqIVTgpkv@QaD*RJxB2TnrZY2T%9^EjHJ$&Ln^;! z5Ok>v!bs{uB2wPj&%QS=?o=0rlGVjhShX(E62^d*9*uSicEL+R%Ii`&yz=V=aqqe; zq`WSt;l*`Db~m=E#~j5=r72vv_Eo99LNVTzQt;aLOzA^-s}vSyhEe+0nZvACgQs2f zHDF#X2h(Xsqdz)qU{#Y}`Pp_}^EF^zD+P1%f>dE4VjFjzDxmA70JSYO(|94?a?ybz z<8JsGFmIHC*|<2D*34+Ji8rZ*yIBfcYmc5nW%wj}thlHVZ&43!v^x~z-6;nzpQ%x7-(8CF?v{ermd|I(vQ8N@ zhP|`?JzoRny;3k+tUYIGEALZ|c)t`xZUH*QDo5*u;_3l7CouRqNiZd z5lSrjt0Ie@hDDkZi=I(r(X+6qPl-j(DYEEquqdX)qQ5J$=y_NaS7OmW6j}67Sfnel z=mkXUqDK7t0r zA=ThxMH+kp4UP(_2LDy0!Kcu`45$C5 z4VVP~7n~cv4lNaV$HSPB+Eu}EWULc6sZpfC1ZXf4QVk|5(qIxaSRGOgCM(jQ78;C( zRD&ssG^m3HM~75{dPN#EK!am~YY^$eL-h97Q^#c^t;=(1ot1{yGl`_0DqH3RFTxgo zvDm>Tjf(7Vg8knLq5aK@>~Den$A-}URz>!=!TxWD(EfHs_IJSk?}X6)sfz5!uvL3p z2<=}}k^O7I{_lp+{QcFQ=!|>5s zMl-zIYt{Zh?`CM^n1iUv+8HP4*2Pqvb@5+%a&{$0wmC+Y-&hOTE!Pp?3u~ z=?x>P71X3!UOXckXI3*it z?lw}{C`!ZO35IN<`kuEkl&lUQXT@f{`X1KpMhsJ6WpH5V7#)Pk<+pC15Wk67@TW*~ zHZrkS8>!xl4klMeoXa6JT=+3%jcjD{iUeNo;7c!VawrW@O&9V4^IteNoU$w#%jVo_ zmTm)A9`p`_L;TMDd0~fexUlafk1-Di&hHiA908m&72s&V`GW$SKH!|C04D~VKPte9 z1Lsc)aCG4OSpiNzaL!hMGXR`(6yPL)bFKoMByi4CfO8~p&R2jl2%HNP;G}?ap#q#C z;9R5tCk>p772p`axkLfZFmNtafO8aZE>nPG0_SoCI2qtvp#UcfoGTUJ z-vQ3e3UH1C&MgXXz6+dN72q5XoZA%Od=EIcE5MQP)cPC*^xS7*QpP~_4_Iw-B2iR= z>(+<@G_FVv_337`PuGU34-Zj2v`)$C2k??RDtgJ5fb{?8vKVPagrp0%y6mr;*k$DX zjbuDJm`)BPveB$DTuLjXK^J}DYMfojY?G6~>&}Y2aFr(F|IhuUj2HKH+5OqbIy^7- z;1`~d%Vv!s$HA=kHa0mK*mqUL=6-eYM@y4JNO7wdaU#uQ4{5QI@q9-V{yP(8Gb^5d z3W(iZkr?U>M4bOncftDXGPH=fe~PSqb~e(%6T{mMtMJT2WKuepDinBQQ8$ND;U7mL zh4%U(O@HHmdC92Tsj%msiW2a@rs%Uz0v6qC?KX|{(Rwtl_v`7nTczB^sT_6u5qRC} z{(N&0=s5nO1h3{r;1z(?kHP9ba#k(&RfDm_VwVicUo5nH-bT5NDk zwHiQ+{a5s5b+6Mw>j45<+;0c1wk64I!c3;dB*scPu>Xp_EMh+au?Oji&5dhAS~@!> zF=uowP3)&2_K=4dGh!%e%x3N8H)pG*Cn>l~%L-YR8LaIZBMWjixbE$aDe7nSu* zTq&~t!#D4jCEw3T#-K)y%sNl4 z^l8N}Xf)vHm&T!a3ca#Dzfxq+8SwCjU0)L)*SqRm>DZVsk4-e0m;r)?hz=`l9+XQ4nEijE-Iv&+BiPEcXzJGc?TZ6!#Ai{id z5$f|-Np;W5V4M6IV0#7!;}yV&(~w_!54vbBMCDaC(bL(yu#8e~GPMf3`~oGrPn|>W z0zj*?*7yaiD$G9@Ez;xue1OSmXIB9bntYI!P734(od-654bH};f)8NR)?;c}JvxF# zbo6MBnB64j1M}(Nn4C{eK@MkQH`48*acy8gWGi?9xtj#%bRl$lhFqssPN(Rs;n~$M ze=m}vRd8MxL$7Da^=k9#wYh*`mq-zefY_x_>^XA9+I@+CbemB@&qO!k+xuDF88%etK38X+P^^M?LD+cT zhZJIGjp;T@sWpgKtE)(~;%1;S`Gz#lYFEa&Mv*-<&u3S*=QZ z){-qcE1?ZJbBet_x`D=cw;SUFTa)GCuzw$k8zX5iCe7>G{)V00VYgrHYbd*k zL;*h=>v+VozrPt{`2u&?5M7H`#<@k2Jv8rYSGMOiMfT9Vqg~maI~3VN^KN!!d+t(X zkA!!!*}&MNA)b@KJLiV!>8|&nyZ$|Jcg3uLYIgQl<}(lUe5S=(&wjEgRg zvzlOMP({Z*)^J7ij5ay1`=HnJt{tMoTcmJVGQ#xAAKBS@?JOV{(Tm*Fv>t$3|B$QJ zP6BE@NYaFK&?mRt0h9~F{t&TT(!0r zQ0sB|YKhSL3)Ff^u3Fm(sP%+=wM1w=3AJ99tJbyxYCR=iEfHFOg<7x3RcnrbT2ISY zON7=lQ0rB>YHcH+*0b`}5~1}R)Ow9vt%kyCzTC-1`Gx%7K&dNSl>QD%uM<#mc07>? zD?Hq?GMSAfMhqh!O~wp?6sYGx>kU#`k$5(;Mz@xZYiTWtZ&7_zAJ)LKTr(Cd;yzjX zv44Qvn_(sQPmp^ntmIw*xwpeg?nRJ$CpbCITbR$aE!485{LPNVz68v7$uZk;KHe8g zX(JYJ!Z4hZdmHp+fNmcgl#df3tkSeBIgHdbJ(f+1^!QhZW)V*Q;G|xKQajKqwK!+p z7!e$PUX!C#FjB8WsU3qW#kC$gRA}#wncA>E8qKm4Gw#-p?Z|J?C{Z2go3M0Ca!Z@K zQ#n1_pE5>tQ{Y77Eh$O_B=t6w`UbgDjm!18Pbk|P-T~ZJ>fqi5+`nkyko-4sdCs{- zW?S_yz`Q3P<~_h{8yu#lYb&=mq5sw{G15X=Z2tzGIl<|;T<`&O8m&$?#B;JmeqT!W zDGT=jtlLeUbstKxu7UN#5?cNT#_djSTsgFll-l+&Z2LF4Z7s`^Rz72&y*u%oNqqvu z_sJ0(@`wq|o7n$f5;Z-kH0XA%%aVFb&#+Q_I=lT@{f_HXsPF-;3T$a^4F_p$m6l>x zE=-4*Jtlkx%n!*i+ZSLZla7`H{T!hGAqQ<`U9}wK7l8bT9I_oL+#}`f{1TuaO8~Ww zTFY7b6+k~B2klsvGlx;Qv>YmKyVd@e9JLL8G+Gw(|A6@^Ehg^Y+x;bLBBW!cqrS0Y zI;Pm#&&V0EneQ*e<=rwi{iCkk%Kz@ zr5q}5f!6+y9M$eG z_L-q1hNL1?TsWLkLyB6n1sfOaoAUk1ObJWtfjNN|^K`5!7UyDvqt%mEi$1-bh7s>* z*by{2aXs9s^u(s8vl;=!Bk`DcQ)%#ginnx|$@qYHG<^CR;iR=D4K1HHH1v+>dUn9f z^*NDGYX>%cEv0B{lc4dAz(&-fr10tMD8iw6y1ueK>tXJjOfdI(3%AsEV}92K2CVf# zrO5W0qFqyI1Z0b4x57E>CGC;N)Fvw#;XzQh39LHK7QtZ_u|Pq-tAkl}gPF zqUt81?I~wP=-OY|xwe)B&2V}x&9>c4vg%_UbBI{&f#b^j7n-qU zg)z)RYMGcmj5Ma|X>ChH%i)CYpoLl$tjcX5X4^?&*34=;B7`lf*Y+4B+T2beK5%sv z1cP49y;eUIX0-#oRs^%pj#5|=%sxBO5aU-bzr^gbz%aEzJP4?bIQuu(Q7Vt7JJWO8 z8=QKxnu!2E%lxZ;aM*>O6Le}X=oB4BJqHnHyV5W#bizmkYIWF;U+S5Ro{s6Wa_N5O z#)=&%*$th)-EC9h&d>V|8!Y$DA!`E#XOZ5FQ<0oNistm)$@QpN2a5!+9`2PjTa9m$ zV@6&t!gMcw*_eCKV_sc^>0Y0*F}vt7k1xV>FCp2Ob7?Srp4PDsLVs|y7`j}tKgqnU z`az+ao>~JcA}v~Fn4{5{G1ON*rJnFnnnzD*BdinHThGYw%EUBW!l+)TJwwTAK0T{7 z*fAh`!&q3V1z{w$ke(F!UduA9)^+qgQMXzYPFj1>(?TC?bNXOb%jR&1Brt^T9adtC z5!PEsyTE~ewY&Z$F4+<=TNFxW`_MNgMz7ZV=q0Y$(vb36M$ZeqvYz!yte`c4(9FJ} zW3-%}5qzp4?^8k|^@fkq3T($Z2*w017K1o{I}+ne%}B5fS@|f@M*P;1`@E4AO5Tr1 zEP7`9L$j%&)NEyFH9G*Btr1Gi4h*ek2SKwnL#f%pq1EgV8fM&R#A5M{i;Y+gxzj_s zdmqbfm&#$uLqo{vFd9xZT}`f3v#{b~_m~EZzEmxjdp@M^bbE*3my;+qI$FENZXPD*jpQ%^ZHDO{Aw%aS3m+Q9vJ|2QMcs;R(A zWUZ72sSV4LnhaTR;of?s6xR^#@A<#;O4Zfo{FzDB>XOpTAv{i2y}uaXwb7Woa2>sf z7yCQ+%W6U|uNIeAX0UL*-G!;#hQpw>vHSD>Hk^?6BHW0Lxc`;(tS+~(HLDI+kgNR5 z!me5V57y@WDDaAs^D4+Cm@2vmB|MV%Ykrp~)47HG| z_pJ;tf?uif6m@tWdzQHwWLu@8CX#ik=aePkgi`C4Xx9Hs#Q3;4mqspyA(Ca4BVtq- zL?d-BBV2Q5kSx*H3$TAUI>_g+S6DDf==O3gN<6oLQ=H{r%^m!oL_eeL2rG%46H$6&PEr0i(N z8&~$Tf3(9!qO}*XZbr zEI4iI{=9!8EdY)2iD|`ufm6etL&HcsUzPNA<8K3P202>u{=i`^!{3&6QO43BVrlRl zu$t-qys!qm6GMacmD(Un-Oi+ReVCWRMXJo~I56vUe_oiGuXXAdvd zG|T;YGDb`AXfDsl9$k(H*zDk7zJ_UG=5IOVLv&#(Yw$hL+RXiV5)E46FZLS2>c`P7 zqr=&4@>7zn#R&l2Tt4U=59o;i-9kR-wjR*$)6XgBj&*w{({aRSogJ!cRps{nffPcm zy@oc3urUS+L0%`JbqET6^4$Yxm<_fcjoK;v$|jN6P_FNl7!D%#(&}{jp|jBUOrg>ICp46Jn=!rQ_edx^n(-GW`YGCm zpsG0!2o0L@VGh1w?3KdLq!?Iyj`DLwIKNPY^GiiIzfy#Ah9aC_E5iAWBAnkU!ug#d zoZl?tdi6Wd!72#Z_2yY7UDXbb6p_JK6V=L(ff$aBfzFbBiLJTNUBlrU>VDML2gT!nsos&RvRd z?pB0zk0P9V72({c2+POjR^ksU-?!4J(44=kOEK1 zK34u4z2;+}{T;ULGNx@Ccnm4=2U~IXf=3Nfyz{XM-Z&Sdl-}N5{A`0%wvU999TMg*~;3aHc52sZ)eguL!3>5e|DlK6uU^fBmjx;>@N^itM3z zD88~iEsE@ERfN-~2&Y{UPKP3#sU$f3@}PA8S%U_LckC%Fd@_uypH{+abe8VCXXKtX zlsYkSsj^_}rfZVW;#oxyY%RoqGaN1JgksaRND!?OSdJ+;4{2SdYo?$Q>sU&xP0tID zEOB9HUWzJOXdQZ9h`V{i&Q(*JVoA0x2{q3F!mhrsbJfh&x0tO*&+HUF>^yE-PCoE9 z7OnN^X+7x+J6FwYXN%beB+U3;IjPedb{;pe-Sgdgo<(d!5@NiM?Q#YmcCMP%N{iM; zG_;%%jD0T^cHVSjML1C`Bgf8E;Hj4UjB`%UJW$e}X^QM&ug#C0olw#S{x8R6&vZrh zus7w$&LZHcm;5{~duAxIXC~|!J9D6<4g6n@%brd}_RLa*Gg}ePW{Pk&SA?^LBAhMZ z*JJ0|@h)8Q^SJzaD@FEv1NMxaS69*o{x8R6&(@0U*+vo097QH+95cY_DRQvsLV8+t ztQdHq$iaAdc`g%dx`>_^3LUh23LT7>8MgoSmlb^4i-wu=JK1*>J7}33kK0MLh>7`J zXKxxr&UIKJgEqd90f#xJ^Elx_s~W|oi^++xVg_w|F@y0E^Loz`8e)7Kp|jz+IIhOY ztl`bQJ$xT2pj;0xLR?CZ=ikjv;Hi~3~|=)Uxz-Y!>!xSSlZsLK^$_L5^3^`;`s z74(?sC>{Qe;_E5IdT@;!g7#AgP*!w#)lw3RUp=1!?I7K+$RfKcA zBAo9j!Z|?^&WVa}zOM-92a0e`QiOA|BAinc;rvh$&Z&xUexwNJ$BJ-HQ-pK6BAlNn z!uhEpoS!Me`MDyTUns))r6QbPDZ)8J5zem_;rvDs&Tkdr{7wa z2K%3md^zL%ed=7g-$}0-Gb72}MOnMWv!qa)>d9w_&JyGHM=9Le3Rw@L#Qr3OSUZ;+ zA%Vpspu_X%5$|bs8tFMSsQ$jkr&@>#a;_X|d_smOwex7G z`P!=JZM99$SA=tcBAg2q;asE$=VC=Tmng!yR1wZ)if}GhgmZ-=oGTUKT%`!-YDGBL zD8ji`5zcjraIRN`bAuwB8x`T)qzLC`ML4%8!nsuu&TWctZdZhJha#Li72({a2*&62UIPH)N~d8A6$cRUO!7E zhx*2q!{PpGD^U0dky50j9)(hS5Gb|B`>wImxOdb9rFsmkx@cL=M+S}+w-luGI8p0y zLItJt7pOFsKqbzhK6hVa6O`!*BAF;TJqew7Y>>;>g|no2HeV$VRad8CSc^Ek{k`S7@)TF;WJML_B~B2tcLMIuvA z!l>TOo&_5=ayB$+YggBI|ab$|bD- z5h^zSq-TTmZ)=ZfWHNYUH=4&uBV}v z5gV*tte5D;3Qp=}C^erzsV;ZMi>#lG)GyTrMP+)0NG3W)uR@~*v>M_1N7Kq=DrNOO zA#Zw(UZ&urUWZZ(X_Z=nnQnzH?oMye>lBpIn^0*Htx8w|G%nE)vl8ufZ_&#XnA6+P zX)juxPQ-1nC0fE5(9)x#QoTbj6$Pz#q1N8CYMqCX{6L*84}^xOf6)sSkkNb4XmN0j zxMe|P7B-&w3_t5-aisD%`F(a~)4yp{s0tHtvMEb~+gZ~Ubzyho>V)|Xb&)Est3Lp< zecYcHUYWVH@ZlJzT%ezpt^bGg`d2{w4{Tf-+(u6Qngwp{LBEkMPG#*sqEUjJ*T>Ln znQI4yPfU0Z-s)sS`W3U?;u9J%I7b`@=zn49zQHZ6=~~CZt-S}9I=Pj8OI@VOzVRuI z91@s)2F;ecmI!+X=e2uuV$9Bmz5+=0IhABlr!k3$eL+Rc;T4H=V14N(i)&`}o8K>K zI29OKcZ;X=Y9sMnCR;t9uSodtF91bwtkHs#<8(Zl(e*fjg#vN8|H0XN37oy4)5JH? zbPLkP5Vi&}&FrsUDyCHM$4B{Rd;)Wt0sE$`AlIqc)@i^P7)a^WAHL40k)l*kRuf2A z`CYwfxjv$q@o3f<)YDZv)r^T~K#Ifjf}^wkSHC2g&PHR}pp(1dx=6(taKSihp zA8yJnS=(vG8j5h%RD`pZBAm4q;jE(wXI(`&>nXxnUlGm*if}emgtL(%oQ)OXL>1vo z!yb#^G_T@#y@?`wro$eBv)qdIY)WGfe|>>u{e*}W$%z~%012;|a`fs=7-lv)VCqPh z8pw@SzuxSGUIZt#{&oz^YnB|nmLW^gx^omA&ZPx%OlQo7UKBY>KK}~LYcnZ&HO@D4 zNkp$wnn3J-#^%t9BInJkRWMFlNYM#3YIEtqXx!8W41pl+mJo^}QOPS*KvG*tQL4Eo zVZ@>t4ZEwjCNN&HC#@+m-@H-?N$P|duu26~Mx~EU2m4-PadV02-9F>AlLfw3l>v&$3TKCrn z^sLs0EN;Pr#&1fIDmbq_pclo3Pp@8WJqcaU=$mH^Vc+VKA{YU&xirK$!Aj2_-84A9 z*`vi86=T?Z#1cu7Oq!l>(wZlQR%`EYGBz3wAlIHD)s1EBQtcX$qVTOIe zMrbJwA)mKSU7@9svdXeCwRom8n$icH^i}J&!;EEEfDz;sR|~r@jkUf$)51nHBc-9v zN1rj&SAFnlInh1& zg%SjJJpI1f!!99f$y79xK!ShucaUZrMx&R%g_a~&v(#{JYNb6+91bN2Zi4y{am#@r zW(EfGQ=biXgcS1{9rFrDtu~w{6OKOxx8eGvz^(0AmorNpXxY#)DbOwXR`eb@+K6%G zh`Msbekq8k>B;(r=fvCAbU-m)LNQ)a3SN6*6=VlooU?IT)FY*Uc5us*QmBJcP%D_4 zk^)-E)FCOT6--S_0j*@JAqBO9sl!r0E17x}4JyAvY6y0IZc1U))MFh1j7GDjNK#Ek z3aM7>mM_ZQrJ`ch6K7=Ud10yFyj&Xz5~I!0u;Le+h-yrkhZ=!Nk>&O(EPnTMx01qz z8_4*KNYSSi#eZ-E8kdeyh|_el?Cj}rH4P+RBAn$MEYSv%vFJc5CvXvHl!lV8RT@|7 zN!%|RNEwEwg#C=8rNFf){$YDep-A>GtFGl)AHP6bFC z^IJ5SHQaqKjObH@?|U6f!pC#zpLyPLtgT~7GbVCe@og!TP=^3vZ!>8Hz^e}he@6-_ zOQ+0HIZfoM-f>bWSvn1)RD)P5@OPz(ZDFeE&OC2wTRcoG=xL18o3NJ@3QO7z# z3M)q~ao;*o3a{n^b*sLr=y3IYDXd!ati*lm2U2)BYKi*RNm5ujYKi;S$x?VVEwFAj ziMrM)Qb?ir6uWLE?pZ&SLMyM8sAHWfg;ZWEaliVJ6k4ptn)qz^V=0`PEl%QIb($1b zwo;-_b-ENzwo>9g^%FU)7Nz6K0Yi9=^;0RVTJx;L{px2@cs1vFiF(%0rLc0;68EiN zNa5AIf0AXc3H#PBrLbzvvl92MUrFKRs3q!KXGme?s3q=Ozb4`3sho${M`MpUn#pQ7 z?$@e+zh%a6$T@YkWg{KELr6nJkT%Z@bv~;PIsBG}8Xti}^tfYgG=bWqBW5z2#VW8U zK=nI%R0M>IlgoaOsPF#4qrTkH#-tC}HLFom%S00hz?Bmm)e?)p*qewm&ZIG&lF}cb z(#qf}@dm>Oid*9>BGbDHNbFgMd73_oTfb35B);`W8cqd9b}8T`dM2ac{*4c z&4L~x{qO>Et%7s95IP+~u2Y+<6VA;ty6IdFaDgg2)QuJw1i8+n5u#DYru7XKeSgwL-fLybhPmYfkmNE(fn8 zWb@j#dR|x1c$eD<_<(ktQfdixr4%TXkilDuD2I|w=$>a)?RDE#G@#sCgxS}&STk|h zXqahsb!bp`QW@O_t~?4}ErnR)LQTiUGM3PX1UOwIg;PtfuCW_sL!M=I6;fSGL(Au* zb@L3vX2~ zTa#B9rdw#z9!2k@F{q|%3kOO&AA!kbcxAeC6f9>!Esz zbE>+VhJsWn}bd=u#4nO zQ{^Rlh)ObYW)DL%-L*t`!l~)nz|oAIopD|k%Ox`zCsOYMRhH}#D#@scJqpG8T|)vW z=C-9zv8=`ft9~K)7?onw#2zOi=6D#sV^PzLO=~oh$_)r!j`$1Oae$y5y{sB~jG-aJ z?2E1v2thp|MW_yI8;x68=}gQ_4ri0@#TD0jr#o`C6h2ym{*(07B51>TIx1>r@(o$N z%RfcWifzS~;cQVPwE0(wbOhY#X^C_M0-4W9q0=xQ1w`-+=Z|B_KkyLK)u-{DpuL~Xzp*XysUl$Y?AKJ3$x+KM5bf9 zSYc{-#yC9?&5Rf#FMz%YT1V2-+7bPzKnsuFrN6sc-^W2PZeSNwA*L~zT6AE?pgChjegllBFTG(IkqmL-Rs{hys4@FfjS zjW_qE-cHJk8BQ_ND*E+TRE!F?qZJyK=<#H3C@Mthe?*kLU8g>O?@UA_M(F=sQH8e? z+?lZz5L;#@hmaoFhZsh+bHvOD5hbrk9ebv97WBoHK`VeEL=7|ZloWwl1&@lXfwRv7 zHFkwf;3RM61R~AIiA{uJL!^o|UXPm{R>UVGwkMo9iCnDUoNzyJN?MXmqE1ySQ72JO zSnt-7x~_LcBG16TaK?{E`*4?mou$v(&Xcx0vx!Cw4ze;PX8Kx! zyDL2ASw(hwQ_yF&(A!xNvlVs{+#TZWOcvScO?;o(226snyeiCgdTqBWveTPxKNEeg zmSAq@?W~A774xJ)IZxIc&(;%j@%(aZ5M^53U5$&uA}leUBmbG5jLcjEoQB<>7hXdb zey_!;h2u0U>m4!H1maP_5&2OaTN_7(1TJ5dTDFg^!dl>DQgX6XXztCLdU^ndv*p^x zk*$cG)i!H`RVFwq2LxwC%wO6(to*IB4luL9F@3F5P@-J>I3!Ceu`XEUC|OxbG|kid z^>hqn$;xycMyiMq`%9~N)&r?k!AY^-XMb6wT6t3b)>$8rBf%kkt>fx2R^yb{0V$_b zR#TF)8)rG{r?Nx6u^Puwj(v9tT8*ByrMq8X(I8OeinFhpTDG{ync1H!S zWvqDO%uN9I?ci_)Pp}Nk7e}u~b4zzi^XZ`Uo#2#wnm60^+8HIkV4J^s? z8gPoc5M!Mt#VieGfYNu}pC?=EEJr#~%+&f(EUqw**p4t0h{uy7wtEr%%69_vdz7G- z@&!;fD02;9TRID%Cs2ageR^RsJ8UG=*-Wvvvw?UbIbypP(XZ`h0R28CsHJU70klZi z&4KuX;E08OWC`0ecOaQcX7Mz4!DU^Lv3iod^uaaADJQu@ z!7&R;Sj|&VLacqZ0jnQTva;J}1%mghs#%-5Qd;xS9I*Ova8~Xw&F||xto-e>Eig|D zj_Ge77gMZ#wgaovDOuU=gX8%Ata$ru4^}@3&dT3D9#;PL*#Ve84UXw=9~V=seRc$^ zpHZ^1+NWhcZsNug;tjMDX#G4mEnfq*dujPwXlH=_A~>k8g*Yw5nrIi$`XwbTtBD$s zw^i*1&yv#`;C2P4Uj^snYo8X5lfQ9x1L7IM5q*v0s36udyMxoOgLCq!(As09Gg&hi z%WlOl5A0R=Hv#z@a>x!Jq+k3!fco3us6O#|L&X}P3%q_u$;<8h_H%<>Sw8a}|FOWJnI4OVQ@TB~Wvo|2m3l8aT90yXY zaTbHr`IMxr#(|TXNW~7&K1;ysg5bP-?bG4oWfO=tYR9_SEYKXPcQt-Nnl9$y= zZ7Xs3jN)zqww}vC?Bd|We9hz#vsaA%M%ovcmjuW3HIjpAi6PcX%R%f?N@Dq0NHd4T z2fAL6x-2*;UkkO)=ScY*XayiI4-V;Tpe6@Wta0`OsVgW+*9&a!O5k zN|~5uR$rxV=7BWnV7@y#%}X^qM=EL}TdGuw5HGCfv$gs>2nl1P=P*|5wXD#C(K?t& zEY4tLQ?)JW9MIDC>5!mahd?jVqY=9sE=Z>0?4@QLTL}_7RFYyE&TQ-x8$n`+K{5Ih zu6FB)9ydnG6wmaYi<#!~J zgq(B1qu0#O;X&CALOp`ib)WOCxx}8xgR)BzY3IEO+Q>$OtTD>XBUN;XAt*+ldf#Mq zS7B0VBB8ua*5-BsVPXcb~XjOAYeC*@;^g!1=BXDci`DSr!Uk>2aE)T*?}I+jKbP7O8&jB}IqZJ11a zuL$5Yo2>892*Yo(z>Rd1bsP~zuJJ&TZj<$0Dg3B5S;xy^N3_ZM9uYh5cpf}@b^;ME zug?)g&rXy=j3RpWeIjPwrX<;I{eXy?w>8@dc3UT5Y9P+-V^agkZtG-lBh7TQX6i8y z4Nd_hLBc23PS#T0YN%M5LxUgEIENq84POA43-;I>%TsAEeY0-RjpdJsSaDLo83S)D ze+;J}&Q1m`f{o>AQuHF&Se_2WND~b09dCZl4{#3o6A&XvGW3NmC^nWq1v7%&R9}w_ zwz2#fI1wkW!tn!cEPoDC#QDxvZw$P#`~`>+q&#zj7n?gOZ7hEYRDwi!AL=+amcIfx zLB6}MJJD<`&j2@yq(tv>kaT1DYfvOjPqchS^)#69BvHUIA5oEghoR8}C z?ss5EkS^^T&x3C)e-B>73Ds=)v{tx*(lX8jG2&!=5Tn>w{sGJ=GV;A63CYItEKno3 zXzc3)6dTJwN?DuIY%Ko-)d;Ri`{v9sH{qgnP; zc=fL~&Ag0Wur(ld`ndAGJOVi0t(OdyQxhhBpHB9&*&SE?mIkyfxHH>=f!DPEl$_k`@&sE7(6p5+=Imfp{tOo;&#V?82V9>r7 z(+Y2+W9g8_g^G;8)yzA<>>&bXT?KMr*3vtuqfVJ&J9wVpK>cY_nMe@CeL(yA8`Jn0Ox+-JfQ&R0pL8T0OvvAJf#5VA>jN~0nWn+A`sZa zFMrthKQ}kP_KlsHXhs`myW{Eyv`4^c0*^HTAlzC~{i~7fv`z#x8OPy9L6dF)wnRnwu6H8pZ)<(e-F;7&^8vQ7LJp@ zKG#2i_^F@FW6euf5ZV51Pk$cu< z!@L9-($Z@dOzqx!UtiK|(91x&GI)#n3@U~63XrZ)g7hkou2zEd8j!A1g7i9&=<^6l zwbL6wqR)&dh4dzn=yM55A-x48`s|2ONN-D7S(QS12R6}XNR&c)7fAHE3#E|$1tj`x zjOHG^bDv@R+mU3ycldh`fb_@2DCj;sjA=b(V3UuNaL*L-Z$Q$YfVcMOW-g^`>1cK& zi6&M#UZX-?niXq8Jvi^l)(1e8aG1QpG=`Hglz1D#qTesUIy$Vm0%yM1p5SSMt_9fxFCsRmZ+{_Z{caR{{oKwV7zv@RPx`Gd{PI0eJ7&2y675Tx@#v5)QZM!kh!y;3K0?guORx%lF7IL0j)S)4hQ_j* z#MSx=yn>&~)ARZtcm+R_cih$bj<8c}ch1D{;@8XGy8Ry@gP*il1KEimu6@rP2t;!t zp#ELkT3`5m`{F_uPVGi?7BTS~>OxfHlM;~Tt)vKM(`f$=}pr?gghNfO@i$+l{b4be?)oaxlMp8}i<#&VoGVfP)IEA;B z!m}7L%xpqy!_H=Uxrmsx&@kise=9Cc=1&BMjcn3L&)RM_zo+VK{TeXa;2UoR_YL08 zrXoAH_1f7E%r_Ndb^!AoTFm+)JLh=qoC?gh6=SYJ??bgkc5dUfb4}R!8m*lbG1mg- z8^JO8F%6gv-E8NUNoxX$JDqET((B=(v<{6o@W}!*vyr-PV|6rZ3LJuWt_zJ`A<&4s zSRIVedN_yu2LT~&09*$#6F1ZuO$($ibgmDXUJV_q4T!vn(+V*FPOBp|C-&{yUU_4@ zA+&nQ{dwV)IzJK+u-b@TtNBbT>zV-02~vi@gG8MhL#vl*wIW~@1*;cnS>s6k0>2w+?szlus zw*HgW)(&{Z@?7629zMfMCGK9>-to_%w{lFpnV5Nq?ZdcLR5zg$LCq<|J_DS#`C2Mw zq;(TX*w&>IkH^dcCP9zpJ*Fb&Y?}7t=79;1XCw7XtY>rzPcXS2S$SsK3?597RYu5Y za~h4fcA0PhqWDO<+A^8mQri+T&GK!6wom}++mCDs?~|}1K*6I7yNRF4CPn7nt!RYc zyn*F^HV0(&4H8zK(^YHKv(9lvcT!u2k<>Qus^DuUk5Z>%#~I)px^oUF1>a41D4oR) zUV@!NcWw(>#JiXKq14`^IDU@erl7MrsbVPJj>MxPeCG>FX?v(d5H;~ps<&0j8r5g0 zckUoXq#%rt-!O$Xq%!eM%;~m9BCp?e?nJIpP)0jLBjQL{9P8x8nwc9;YJ&Z37jl^* z!Ab23r3m8Ae2)XEMn@^3KDV0`p@MPR9Xb)532-{qe8lM_)bGAYu2XPMdq5|G_&%po z3?Z+*!Rvi;UTwTy zV*a%l#G1oMYzc^cKu)Z6p3W+x1@N$aq>Ns{c`b!rACl|U#_J{KVaq^lau|v23u6Bv zC)TpeNNMbvwdnM?T#ADQrqv6zJ|b7El~YU1y;ew(D>$$Hpx4I)dUYW&%l@i4-O9d1 znK>4I)P|k=MlML@1DE|ls?Pm+@s%nmsg)r02>~fbr$}UC6sy!;}CUg>&24KE#f{V%v)j;;9K_9A+v zomyC|n4KNfPa{TkpaUeJp7U)M9qP@R$>GsxKdv(;+-eK1pp}Q~1oC=Xu8NjSXtQ>B&{8aK#b++Fp@fko)q$7+83kt zTXfJWxG5^vx8Uc*r)@;cj-}TOAt}eZvbvs4RKLLbHsm5c&J9lLJ0L}TCJ$2W`F?0A z6^-Lrf$HTt4lCt#2v&XE2tmZ`yI{69EwjxF-W5w}BjbwQxrSM>yW{CyJ~*lGL8*0V zm3jg*^gPWRbjB{B`S1jKu}FBGNY9Jajj+#4^Mk#RVBbeWu0`9BG{k;DuNWdU?eq0i zJUJX4!Q~`96%~@~BuKU)tz<;ZP6o3LXqmM@vmRq;2t{mzxY;RSws9z#{SeGHqGg7l zsgo%a*V$`uLT+{{eFvZ+_9G~^9)V(oQ)iYl-C>>3;}9ZoSGrQV8j2Eb?U&a2ENHlt8KZ``_ z2m<5oDij||-jx42aEK$`R&|A}iM-#aX6jE)ho~#*2t>1=~*zf3x zVTA0kE2IX~?Dy!3YtnW_Dr#qvsOC8{M+MQk1vzafnk`+*Y9nq$VIQ48Z)8PA{y5 zvc&om77-+AR;bLG|H38X_WY}k8E_l2*p*t~xd~tSf9U*qj>YWHU^bDUCw3K>VVh%B zix``hn^u*l<@|X!i`LnoMUu)_pmo2MFvlJVADh^GN8$W;=g;#kV&{Mu#mx>*uQ`~o zdq=dP;bf>h^CU?klW-2ak6~nVyakgUlaf!a zNW{|VJd&P>qWSN;zdtCY^P$p|;3~O%@S)_6;R4_gTsWwRb0KgDu1QwJxd=E73Us>| zI2897yVh__nv76TyIoy5(;1DKMka$*@mT%K`IpWhX*=^JM9xgl>{1eDp4Cc6;Vh@m zNM*-$mgAM{GI~-QqapbtUqNPnu4q43_|RC$rSSMVfXYs70 za2EV+Qdl`JYzou5T@J0@WNH;64Z^hUkVC86G}1>07)kGxLaTXcJpY7RM%M*4s&`3Y zg$xYq9uZD@k2LU{orZ+(^1FI>P;fq2gTviYm^JpKa(Xm_%}`2!(>?T@uvk?4sKO7tW>ryvqNMNcQN7yXrE=>(SOAM~7pNc2y7I)Np6fu2(k ziC)ASWOLdzNMMOx0;kPrIR%mEWzd;TODC{IuYl7mT24VEdKGlGq@@#BqSwOJ(O##g z6Ih}*&}>`KHd_!cdJ}ZEp`{a8qPM_lYg$f0BzhZkzClYTute{G(^j;cf=Kjk2s_%p zAklW=k?1|p*_M`0U@!VNIL)Eu6vT_(51|)*K+h?NL?5EXqO>g**o*!HPSa>P1(E0@ z`W6c;(Z}?hf=KiUJ)OW3{g<9o5Q#p8Q*T1+)PW`X44kIZatb2R=kzTWSfVfJIR%mE zOSITb+7=5e(O2NqNy{mSME?VwO=;-_mgxV$X$CE)020lL&^vWdiDn@Ot!~%wNHigY z5>13eyM#xgNg6k3_RTr!_d8NTD!L%@(Msz7mDZQ1CEjpp4QuYrME+V%TlVS+l`wpAhoe47?~R za1`{KQilpJ2kO;{g=VwAEU`8Rsinci;;K6lklF&IC@$0Q@$sFf9Wv|VyCMDl!r z_aEbfWls73IU$5?7@FQC?r^3@WdwG-4LcEnMyEnX1hQ$f|r{rXjXn)Ew9Vx;DwP2nw1}8%WJlW63s-IbwM+Nr%x(qR(_-{ui0EMBY68q0<&%~ zBX}=~n=vOvvE#O%hOfBPZ}1HL`9D}QKo58kyh%jPYaVzJynHn#ukvGTdA;@wDK8Yr zmfnu#$Jg?DEdVdselNmnA$XCD6mHKd?vLfi*7ABS0xyDW@-h8hgx6l+MR3JsOkU+j z{PKG34PFE{0?2tS1}}o^H?DW#175JG?>73Pc!kS!-a@tN!_Hcw5^NtL#W~Z;TecKn z6mB;zuw?+F=zHS=+m~p0H!iT{u#6y`pTio%8+%n+wgQ$BTnZSUW&5eLY=2l5Jbve? z#N3LPgO=sYVOEMcY3ixuKq9M~<>JGq@>9kahIW(O0gMbGRIXhw1Qst}<2T^G8VSF@DvdQVYvmKW_% zD$(2s9T~O5pc+NxmBOKDCmtvEsTMPm{nfkL;Y6xQpmqdQqo|=%ptj2yT>o694fUB? zOm_yVDqKwiGm080^vwFejG_!uSAIB(MA(?t=JrpcZ_FXBRJ>`fHKfHrjH22PIk7m1 z5frm>^@>E;u(5_mu|6Z4HHI8Vvu8w|rlqRJ?FTM}?-m{hVq=l(Awz8HOK8rntEf|z z@(}wdeSoGRYRHKtK#an53r)2T0+KC`hiF5&RQY1!rS!6UR1&mks|=CQIuf)fD*Lz{ zePT2Ele?-KPH9auB{b_Z;-lr32jw*z zqH0ZYW@#`ZaQ}jr)Es_#HZs-emHj!qG|Em3+^%U4+XhWjRSP=|SW@4ea2v)TcLh0! z?IeO_daCS;6y;syD54dl+pXxRnNW@3_E@2@BJ-YukIc13?5NQn9Y`7G=&Y<8CboSf zL!(Hwuvu73aGkIstb2%HTbom4tq*n;tR=Y9Rnc1a=*zZtM3J>V*wwI>;F4cOYuy}3 z+uBh@*7{(NhP4DYt$fyY6!lvtamqIK7%9e9Lj4wOCAfIU**fJdjO@KxJeNI?O+@YG z<*cmN)?>+S9S`caVJpGiC(hQ1C&1Q1yJGi|jQMT-4!N!4K|Kz(Qe1^A#0Xl?LK?$C z+K^#tgHbDVVLDsNqHPu@QN(!>+TLb;mqraP7oLXK@z9H++DsR2vf5vB5V>e^^on;T zl5xG<0@(Q{?7LU4?-9vGOzQ-wMNz(}uvuOkaq4DAtyn@E7VxbTsl;;cx=^tCKD45( zKr`iWTuE`Y8YAvHi*qcmU;ThcssaE3sgs}-MLDCwn8n(!WkD{RNUp9lWR=(IWFoDI zX`KSKC~DGphlcjNT5(ewFw$cUTIB`%A(3G8%ua=76s3;5<5p8%voVLQ@^bx%N-oc^ z6_C`Ap%iVc8a6;QI!Xx*TBi{S1(jgLynYG2<`L*sa7Q+JwQza~4Pd__l8czu8Bl9adbQ?8hmDcAZsJwnk>tR5 z_CBs%)|u_E<){^u)^DKJd;+x!<0|v5*7+%oDU~&iTt*+?&e|*2Z;9lh=Jh-1wSZi& zhGn=>r=<)dJ+6(SSE}Djktz_SGojK#0+n1tBi4qjH>K;t$@D-preP~o<(t~G{y?M? zDXFud)FJ|XZ zYk>c=6v2Y?IvaXXR1)&))v_GJdl3IRM~Ym5X`KtTD2fVs)oOOt67#L|q=*%i)%nnh zqPmb*D|mcP59C@GNKq>|uM43UL75@$w7^=&7xWTyu8X9|)e@A}#ZZg5&Jb3nZ3|3Y zPX}_ZOQh&UKsC=8yFrRz!Fk;Xz4j;9 zi`#yP`PWTSMF9Kq>La_tL7323HVh(nj6v2Y?x*d8Q zNUm2qw?-6ou{)$FM#1b(Xm${}W}c0Rn3LTlMKS_ncSEs*$rW=hw+C8`-6KUa5^DEC zwL{2N^X^8(9PK_SqERrrADSIXpjnr*6^~3k$&*PDGmWE@8FdxnDy`T-K?k7tXSJ;a|b4r7W)k+8Cdt z@Om1&;$?a9+BnSkzbmJg)7`I;4KoA`gVl`&~`#J`gZMn>&t9y@qQ*I+AC5-BOvxF6dNR0tlcw~iAnaF6v-%>(|TlzTf1hm z2C5g0GqJT0(RqVNF=}3KLa!lmy_{vDpjdB75i206x1m&;Tq$>zC@9rCQltvX>Rl36 z{wlD|`3#iIh^}V^lJNgU!>Z5)tqQPKihF)QuXrHwv`hk8g{@Si0mU-WOS2JFF>mPB%qYs@q}@7puHlta$37>b#C0XOR8zkFvbdD!Ok4I$@dX+t zE2;<-)Yv9g%cS_G>&@S3)XQgVxeMw`UuLC1VV-MTz~7o*4Q~J??{|UaW2k^chCz|AIRV--2M&I2Z5d;&Nj$J^sULchJMb z%#pe?X*r?Ov@J7I$xH%`*Ox;vVS!7Rvtec3AA%F(dshU_w<3w**z7Y_SMBMuCxB5l zIHQ_Eo%@N9s&(!vtuqsaPocbb))?2T4I4RDv8it)5b`jCa@lw zT?;~I(-MMn6>V>3V;ZeHM*$8Tv+i64IQ76ePXSH?aLx~m6Nz+fGX0r`-G|SHE3?kO zAlH{lrZM|w5}FyWe)^r=2wtlxcoq5>cr`88**K}2V--g){0r`rH6k^E)P=!G@!bog zT2?00+1yYcSm6ZPSsb}A6&HxJu|%u8$o+ZI1sl(bwPAox-vUAxhlvml{_8FYOo(#_ z%gYgvN^52^gFTReOCk2CWG!lEw*m07;DCIaph-k`rL)^X=TdSybv>Bc2SuuM%%{Vx#yUP7}(cbKjAT-a-X067GCJcc! z{_Hiu>dMfuS_`bM2_37o!RqSJv04YLt_vNjb;0Uda#k%p2;LzKlr&H|POHA?((Ls> z>xS^rS|7Bo4~iaFA*a>2P}fpyVJkGeZwyX1hm2DcoNf;p zr)l7HN60vB0#3JvjMH>*x{aJueg0P7fWU~pDHz>N&S;8t&sl`e4Dh*!oKKy7GggGq zOc1&=I3a$Mge^@Ere#JlgCH6zOu2Y2NadwB?pD;@ z@BX~-(%UCNTkmjkP>+9O7~Hnvh}l9QQeF2za7>>N^-G8J0gVH55h|;&85rH`{ydcm z&GYnroyDm68OoMma{%5K9MC60v+E04peo9ahpwI&``dS1gO( zd+!i>?v^bity@4Z8)`MS6EIN-}?*e$aDnIwT#M zTM|~XaUMwj+7AER1>f7@zkP>4=inPFf9bFE&Fh%vO@BSuh^;3^&S6b45yR`Vi;`*q z6i?Ud&(`A+a{OnLYP7@XbM*bvRnCCQ!_1#|+zyj^IQcoX1d`6n=2$HMcE$XnW z236Swf7MtIZVb01Q)_;NQZMy-LEjd7j`oorRQw^w_(;$8&~vPh^fW*ZMNqn8owr~O=tJmVB{L3J$9=Jf z4o~GG{rMkLRvUKAO*@&tt)D?2GmmMv8mp-Jh}7{6k;?YlKH3kv!sdtLA$8}ygG~hs zpy&h!MO=-)s~MuJ8QpDpb$iSTu+C&> zm1bDutgzhxJBuAwX25K;-9ck$R7eTK3BP%<8iBTZUlrEyYV zbjVul_6G7C_egT4x=% zoEyh=@;N&Vi}_jik1*nXu32CmPxP6;G!WS8e32df*ZE@n`bZBYB;Fm1&Wygb_y(Xb$qe+LDTlKrTQIsl@W4{5`^ z$`+?^EUW0;o25&NLlW=tY<1c5(7ByzuZCf-U!Sj0>c8m0NJ7`;47#$2k{;yGvRFHwZ|Tw!4py&HkaPuuqy|&d z2`W^}&@Y^TgZ+`>9WouNmC$u1t1h*GQ;x+)wVQQyPGlwoA4~_=v#$r5+@HOszrW-g zXj&c*<19GMzjU*_3OM&iu-6#Z3!M9t*lUbC5IFY-vI8Xz*sh=cst%ywrC%27RyTsI zZaK5e_)D2T^?%nh`5@?GIKW-f5Ht{~6|7l=b~y}DF!mHqCg-iGIIY zYdRE~+@ImLoceqeGj}`oqFr{T=rAa9e~8JjMft%$|cV5}?VOx2N4bt^;VBgGV|s&`05+aqd6fm2;aK^OB; zYo%*0E_;e6UFbR*x|q+SLl-vtN6-^>9R>4?X5SLfuoRfn&?~|d6ec{eo8(l71Es|m{yxxe%-<>}DG zdd^(eRM~_}ilXhwj=ZW2NqyAms?LBa*3-(mstWydAr26%u8{W;q?GES&V(q|MV$2J^!9AQ;l)07o(Il)nw|@HJuGjtmof#O=U~8cFgk^ zBT1K~OF9RVSkEo%lB#e_Ce#ww+ElO7^v(1{$GIS~o?T97%)(R@_is|1*?}*-1b7}a zF&uAKVNHLfqp&cX#IfaQ5_eqLqi{ZmtOwY2V#TcXFy`NTDX)kyTmVrFXP2`BL|s(n zLR_sGi8pTHX;3tt`f0jJ!>o-tVA_;k3IwR=5Z;BLc#=0)u`7VJb))UZrsH*3vdu`*XmgGcQ3Deeh zDUhs(;&o*C5-bBc&YH`hiS0bjP*Zmd*QKjDTC;a( zLJEtz4x;{T{=9QhhNM8Ce^71nP@?d-w?kFeL)CxWtICQB48jaV&Flzno>X&ap-AiK z4%=-LH-Py(D|2K6UX+~Gs.WIpPRpuWILeeQ6~0jWVlKIBb6zQ~H){^35swcQ?a zGng;2GIIvkM!f~pmszQOgKI$ESW2N>Dt_}Hn zAYW%i<_NCscAq=Ie1nylGq^VDouIzSO6?n58}cq7-(p4P2<}wt>3~0g`8F#vXK-!Q zKZ5!WE46QMZOFTUe3up3e;GWtDVb_*RgZw+0D5qAyAfOyWVHWubAAG-Ys!rn7~ z-f{a+TG|}Kk`9FZ8N%LY6}BOsB&nLaWk(omF)Co$c%_Gw_kj2TJF%J&gQLwzq>+CC z@cE8}TG z>uYbTs~}mSi?x5;fV6x~}`6>r?aR87gkcbN8X+J`ZnYpt}lB z$u6L#%j*3Ae&!yS9UTV(*ceoi$)KWl6OYiN@Bl=8?p_pL4Kc#2Hp2~14;0MJaHQUv zEF*{X4RFR^HvZJLJqT@Im_P44hYD%SU%~ThdkEUTbgzwOCKkG@7h;QVkJgI;58ah% za*UW6VY2+G$LwL~`pW!y$1$V28nFPcUq*}FgAQK38}kSxea$Wjuh6Nns6$PEho*1% zHKq4}I@I(iG=0mfN%@0Xy2L!f8vBE_A^QhJedk_OpkWK6tk%IF^g(@ajqD-+=Fi6< z?R$A?`Q0JEw8tUszZ}wxpalj5;ST8{ueOi}$?S{CG29`)Ydry7KbSx7*d55Op}xAz z@w~|?tm{eW`qBJ(8C~X3*yI$}^%QjdWd6L2t|@u@_WASiY3TY{SeH2fGdYFh^$c|V zV*WfwywsTveFa}XIkDPeZyJ|bY4AaB6#ofXzskzWU+VMAdKR+&Co3y|q0cYtUx7er zKvq`%GM``8bC6XcD=UAI&oArWkkyYv))1rER^F%Hw#>|W7c~3)jnIFftiQOj`~^S1 zvge^}fVi^!Wk0{N7ocpQxU&4kKfkgUp=^-2vivn5zp|I0Y%qf|qx|E~{y6Y5JJxL3 zt(_i3zYN$AuVAkLR_Yb(Rlv%;g1rVcH4LJ%n$%&Lp*yGv0~~QzZ=*X5*E>))ltGy_Tvh2y9n=$d&h7+w7qW&i z$g&2k+LX2W%iPJ|%6`<#`+LweoI#g0TtiJ=IH94oWji>c@jjG|U{{vSNQdW)K7g!| z?6NYKhM;^oJF5R7&qX!a!l7UPQ$9~isBO|XFlJn>Wv1L1YSO=Ae%6cDM;JV{nm_M+ zsA>Mz^gA{BL}wS?0>;}@*cD_sZuBuSXA}dnfiTgcWKO1agfs{D^?U+NYcXgdlF*`H z#NCWpOgx7i#If8xynk}Y`nkG z8w_c22mbRpB(1|BslgmJswNK4O7|u^j8bd<`oakgFi$U%{*bG>3ubO z7wt=)QbMi~R6%h5e8pFY!g{Px=zp}OquQlT$JnFrwa=pP4Wh8V`zVl(v${B^Y8Li> z_TY3WRz`cm4h;WWpM~i=glPkYFlCQZsxVd0$2oJgcpQ(Va1!2uvwV++G0LEe>w4?| zB2F7J=&{DBtP%ZqErLs{9E9ixgs6)_5?6?RM2I$G(35S%RZ*&FiiASAbx1$8SbU@T z6M__B5XBXwpAn>>dp+401yqnK=V1(y*qO?8i-z6BPSgc-LPnuvl!hp~fw79tFYO1CieJ08l|LR7gEo~mkB z`(B+zsnlmt!rp??E!m@l2ffsrW=?}t?z13OAV{OxgM^)HxHQvgj4FK=qbkH`410_! zGQ0a6*0yT2TO3#2a5daD2+~;IAYr#CR$0`AqFQUS7A=12aO_Z@l_Z?XEFH%lq$*?6 zoYOE3_gR>5{G@ceK$zmO9`zcf(=d(nS(rv4OcQv+bSJ7@V;Gk|hwy@olWVfauH~~J zt&Jc}bRQ(L`~Q~@u+X{(uCrqg zy!;c_T4Ok@pN>@~PWo1tvFrIP1nVONlNmygJ&vJ5P?OnEVQvO5zN~EEvp`|1RB5gI zK#>ck(}4;{)KNtjf!fGtfeIo}bqs;ZR;u-OQ>#v9rBXLWl5Vx)|>jBk7~2C=qgk-hEqEa)TTZQ)Mg0O6!t(>XI#p8oHqAaoNykebSgufvL|Qj z9>pr3QJ`A&W5W5oJX>J{f%(S2==E+(pM?oMl+tPJVXDbm)anC8J4n_*jqzEaFgss5 zojp)PGxe@DuC>Ra>g0L8B9%W<<9rsW@rcw6hDaIp1{;@fYjM@GSTnkj9qP9EH5{9m zA=7K#1fPXzBEmG&eV8yd+4T9cZM&}u=soUX`o%tGA8K_++RgA}1D!rkG3%DqyH;L5Ii4wa2A`J`99 zdY=Vp3WBr^dyp#Us;AhTM`^0hqBIRr+Lk>^nfXfRVVdrt z6pYg3Fi!LQDGO5?rQ5r2>9U0)px(8tHp)|u7h1xdT!; z%Y7ZJj$(ygmS;zRh^iSO>cI1hOl)S}=-t2*2w-n!Kpo0gf2!|54|ctjG^kJd*Mp5%iCw7f3c?BLXb6vh9vH=)(t))X zC9!<1rSDUr|I4Hst0U8mUq%1agSmt!nDqDJ;7;k>SW60-ZN7?~UI$&u1EWKBz$%u- zdhzycE1rf4<(*Wd0?p5Q1a{&PW&TopfT+rOUES&tb!}C!Su=W_=_oW}OVe#;7CBx3 zvI7^V-;~Y)+!?@I*@0C{WAQG;Jy#N!fp`l$5l_Cf<%ww%fH$)Pt7fMn`eRW@qdgaw zqngigPbAemfbKy}Q#gVL<@d)0`#7|@hO@tBNzlvlE)X`CLm0M{WWM?c%U+0Sf()xM z{*L8OUD&P=HqZQd$K}3+Odmkla7)CJsuC1`j%e%(Xq(TVts$FiXd7nH=Ilm*vAaRq z0{7Cg!UFvV9Rj+ zC&|g*$$r$sySI?4R96=q)Tyd{WK_*cB~x8d$Ex;~QMCe($+?KterS6yW~jN@f}!kU zn5A6pp9;4aeqo}FB2WHe_M=|7G)TL|{CVe1PWIQPrP0FWP+BvjUCJ-bQo*VVc!dBkZJIfNZ2(@$56qN{9Kdz} zui^)0aytjG1Hh|!fdkbBTFY3S3r7)Iw}2lq(Y^xmw@FKjkp$;V4>&1s&hUV<5}dPLbNUSaW{0D#YV_9@NrfFW zH$6~wmU~t9+-wY+??0y7|5f0e>z-q~{h(BFoLzc1wik-d;ZTILJee<)Pk zhNcS{G?8{hb(7M$*Q`0%hH(g*k7W#sNZz47S2jj8)vea>PPdad9ST)V{HoG-6F63N z7*s81S5>aga68s?I5h3TuBkG8WZJQ+BcN(mc2yM%Vi6aiIufE*@QX^n6YI339|cjn zF^Fo&RzP=M@S|Z-$1?~b7BvOtJs)@374PB4K+JLO#bjfnw=Dg4G{@cPX8u@Y!tU-d zw6Cff)yBBii}_J>pdE}~kAtQ?vYIf1i`%~fxU?m(7fnu%&zS2b0T<0y63T$oRgsG2zE_n>aBa+?9<|8E91$Kbhy7H zodQXRu}i9)gG-{q=t{ScqGD1C&sv=dRfn2C&oygRK0B_uW67A4i_*uQ22F>sYZ@{; z6o~})chYA)9fA&G7gVt#7HLDrKAcG6QHi2wK+k}vgV{w@HFbo$^&31fE$if9(V39d z>t2@45c{;CX907dYm9xirwp|T>)G8XB*kaQ*^t!ZUXm^4eP?{m0cVv5oO8iRdB8aj zoRuDM&Ic#qo2BU7JR2wow`5JQm$G96U*t-y`A%9o zwU_-i*vt8`cjij1`CeT*wO9N$*em(5IE>Y|B-a{`i>6b1)o+8nnjM>~0@c~;?KQs* z_F8squ6kQH+E#C``)#n-!;_<|o*c+gZ|g?e>g^5usa+H7jr`ag^|o%bt=``B+hA`- z-ZDG^U~Bkr>0Pufgv}rie)>PW+=AGUn@51cxp6)%D4xVrfw`Osw3y*&7uU<)Xxb5Q) zEU~am1gHtvWuYFmsVEt9@)p_HKl)MB-F_7HCl*n3yN5F0nx$Ge5^N6PwZC8#9~~UD z`Lkbz-GiFA7eh@X>t(2kRiq|5IyChc$l9A-RyC1@&8rUb@vqRe4}&h!*4o15Xyf6% zEX9l2w*KZvQTO>#)cq(``!bX&=1TYgORPvy*B43$HKr0BDeF4qgXqCByi`R|Xm^6sm1qC+179$;b&nY8ZYi#O8@GlWb!{(@WqFRG(Dq>p(osCVN6OmRr8|U$H?kLfb&-Sy0iG0$_)xSj) z4W5sq7fxOl9{V32JtUa{jNYmj#8b_Q;P%*F7wp0&GfS8Ac@bpUvY48TP{qYUdqIST zV}FT`cr4asG-By~;EN(Ke0tPDDSDGX zLW41K!)1~y(ONpw^cJENVu%tk6I_;BhD&U^x`RETkc0DpZ!?97QPMkzQX6lSeneB5 zz8)^3-`VEK1>WTk6+E+Mj)qfl?Xl(v_8IqtofzhOeiimUk1$fU0&uDRbIh)XSxO4G zDj)br%ZDi893y|LdpkTS^bt>BsC6Tg9E$l^M9e@O^K{axec~%MpNgm{ouhSYE#YMD z<4l&SNB>11WPOHm%sdx^qT@!<=S(rmPFyL+8Ms(oJ5zkW@e5z6`BFs9kj7X~DBc`X z_t{4=Z(oEZf8{qpU!y#;%wn%^R24OgUD+6lw}o2NREDGZ{%^R#G=QoaZmLrI3Y=>D zmPeZ*O#O+~vmr}j5gZ0HFE2_DkiQdQm6QHgrU<8T-}B(;G7FMcz98Mxb9S`#zf79Q z%?z-&!HwY-wTZv@8vFy39+Llzd}P<#LCb#szpZ5lJfF%a>{Dru=TL+boN^tBxz- z!O?OC9NvpjI85F;>F1B@CnBn=3P%&kcnS}J#qgonzS}tT zB}zFa{7AV$J`Px+MT?)r8aEJPnCI10p;{De!`&OL!5&AV1_c5`0=$(SZFTwVkk%55 zI=cgF9HvQzl(^RvXfV5ysA;2NxZ$89q&dhvY+op4nD5Ics?EvGT;ob1ieuTw z8mUSnQaxBlbWrPXiAiaPT>d#){2f;gQ4E_!c=N9UqV{Kxl-vA6lV8fQ+0a_5T-LuT zh{_Jz9rsG|<)5=cRn61(k>*7?Rp}C~9S>iI+KshqvxF3!Kps~E?;K?Qyz?*-NymX1`k(}7bBgRsGp!a74LZpC+8 zhNHAIpIAf0%I0BH1okL8Dp}a`Sjv11)UBkEkmUYW0#elVc^y|#8U;zb7Z|9b?n=^H zDCGTJ7jnxOQ+4s?s^wffv+k(kt<7Vc7E4f9ipEo|a04yA56rq&)mQ5NqL%pZ z09H+S*KkOgdrc&%`Xc*|q2gy<#|3%HEsK_oeWYa*A8FatM_M-H(PD3`mf@{VOh@2a zXWZCXd}Fn_FI8;;KXiW~RUly7G_ova-rR1QNZ|%*OP&zXijK}$Y1*7|b)BUp7%c*; zm=(vM3lElc;C#G=H-?8tOC=IFWL7*GYSUVbv=tHUu{?ro(H=5uRfta6rJLPxJS@J% zj~5YCtseFZseSBPJL!59ktq{Ibh#)c6Ga47;~vcrE)RDes7WHaTm)*eh@h$_^#GKM zIMs^CauKIG9zizS8L&h%9~MkoPCXBY&vK@)2%>rdBa(rUnXMlyamqw%#~6g3k4Q&i zD%yDWH+#}CkJ^A#F)P{<#_$SbQ8V%uqpPM_l=-Y5bJPbt(^#TmL7+LT+ygs&I)kGV z5xg^LxoK}49#k!Lf0d{m*ysM)aWi-lMn-cC(Vod9iX|@>U#}4y-q(i8uzEanQ8d<)!rl$+>r7`&@%_D>kuN{9 z<_njyWr!K?t5Ma@qLpmyh=rozRl#Ver8AOekWBN_@>%a!G$CZau!f9D*m6XS;ax9M z$Es9^XKq;QibdV@D|SJ|er1gqldxS8F@{IlXc@`buUG-jG42CEI5oIYWZ3)|R=ATL z@r*H#o@$+3*^Q?=S0r_Jr1e<&v|1k@8MlWwX|+-JL|S>DUd)-V)@#wn?d45cZPdMy zR)#k(3za5Y&ut%X(rTmbi?p5~UxsZxxBXy~?|4qtkk%7fsY@{tJ*=q{bKeQGnYBG& zFs>PCeamxd3(|U$Jk?fnT9MZGJg0_`)|2I_wwlw1w7%;(wH;|aMV@M_uN_G12cA>I zNb9NYsic>u>T9*Fz7{`{*dNh&--o(75wFu&b?tB$;x!k$35;QvI*eHKvAPK2^^p&C zbs=7-yVph9`P^gTC^%=Za+Xb2b8WG%X51xdOoH0mofvz6Voj*sNb8yMX|=UG2cU#~ z;yE>rw4NnTwY575r1fLZsY#^uYpLtGQiL{>Mo=VysRdcFs^|cruovTmB z^dK6a`cT&@#Oqv6UHwPaz~5%$PTEks69Etnu{z(qs)8-`A>dr#o|CQldP%NY7HU^Jw;gX)IFzR; z=azLCqIDr>wDj|Im9x>WL8VvEjydUV9u8H%<5h(fh?*U>xYo757R0^qD?^E3ZShs> z2%cQD6=LjzONI|fg>W#-e8|L1ec?UtMdKfoHsn+T*Q(;bO*1>pXy~O zzp*&3p~D+4IEYugdtD4my~1_+0|9a;_5iYHswEyvB${JA#S6QHCt&=-E=9zy^kc*> z^J~N|XA(v_MkxEGOG7<)#Vvb>sF^2)N9$MkPSKUVQ*@Q@6kW}t$lmtUEYu=6FoLUS z!)@Uf^mL2&s%!iz>{=FK_Ex8AmuL&#U5%>Cj&W&F@$tG&L{{0Xcs$mlt{F8ttwlJ( z^&*l=cL??1TK|xPyTHfYAfjk+qq_dYiJlup^bFYv=Z{)q*pyoI%;rrZg3R;K&P3fT zBC5(bq@C!+gQX5`t+_=+R+-8?r;=_JkyN9Xr;c!UH{5BZmT=(axA6!gy%jZ;Uxvpj zL#ZxjSH9iOFG)>&xLfl0y&py0;YU$-`cc$feiZcw46d#+n{CI-Xr%6y?x&s}4Yw!4 zxUjZ66m8XT*Trbt{+K@kd$k8x+ZLp|fxX59tZkRqpMbsA1FY@n!=Hh@&OMg&-*e}! zd8Wl=6&?`Y18vuvKkvBQ5pBxztLCU~vZHM`>0Km$;i;okmYOwM5D&MZ=N@beX-Uko z7vJ^ztB9_lOTyZL@dGqW%w{TT@yhPyQAP|z&9jxG@y6BQc%#eQNs*qR{+kG{dRaw~d&jL%~j6&F1KeS{?o zX%6OIbNRbRoJT$4{KF&8V=Nr|?3aFD5{xqaDs%Cr=5Y~K<>|*On$h2IF#Gici>4A% zl=}zug?0VJ3vSw7vlSp*q?P{CTa+hxL>a$R{R!*tyY1;I*vs>-?S-U059euc{^J4X z8IR)jPZo})diBQ^gv>FiHg)@zUUJr`JX?#-vnqt6dk<3 z$WnBkXOUL8=)B+&=S7b=FL}gynVqAWD&ugbZaAa6t=rCC5kb~uj4p#5XJx)B!o>c% zY>Pa)dz=mW8U_G2xo@FJ;V=0{Jz0~6wrX()o$S}4>1OwuNH0KXsv?@~+5HCE>A$-t zk|+dz%$6_}gM0QD?8FC`5B~*`8Jrht(J+q zHdg=DgFB0}7w?ul_W&-~pR2_?gZfUS4Q8o7aTj;2E2sq_LLH534dbHs1U7)0WMj&M zqO84)cTi9sGk@OUlt)QPex+lZdbI9f67O6GqcKchCU0Kb%M8ah``y9gV^P{jYAnf1l z!bZ%)b~U_Z-r}yHeaX^p+M6dPabF>F|6-5au%%iug?SiPt>@Q}_MAVZeIt^k!!Q@y z9d;SDZ~ZInJ0@w_Zkp<+4P6?J;#|0^qV~OSmHii$;7R^UkQv6h3fd2l_LM)R{RnA~ zGf2xWD)-wBcYExBd)0B11TMeF;hExx13zJedOL%vZ24DD!wk=?;H1?4+_p$Bwv9NL zF8LV|`zNb5egDHEH^Zl2py^qkY5EnKo@UTw9cxx1PS_F9t@eDIFV>orTtjoS4Co`^ z|3TF==FfADfL*E@4~?b2XHaFPH4wmTeL40tcf?lq24mskH#Cng;dgr1n)2VL>fpw3i|QH{?VjTYpfugdP@1xOlzTeYGZ1?2 zV%4J@)485OKFcq-R_PtQ@j-9Td3=WO$H$ePQYJlS?Ft023|?ilxCtDgjW{sKGJezE z5r-}p;wlQ9HFmI)GrpWZP`D1TVo4Y?NbPYvn1xGKi=EmXU%@X5wcn*EH2f@8F5B%5 zJc@>7jOJQ~WDz+G59KMy#X*N5rGMm2DT+i@x=7?q z=5P@2&P_xB)XW4%X92wa6~GHl0lbqHfD!6{n!RteI+*?1@aV%4+w?eE-*aT#q_FlP zwE-JTZ2Iao`CsXHkARRr`9a7??u^eH5q;}iI`yM?pxLF*@&j&5uaURJqL`>%8BX98 z+~VzgEuZRI8>QvXxl2p7D6amKwb%v%qchRT{E^O=b-=mD1J1hOFsxM36z5FOdf+fD z))nNe4-Ug}WkJpc;4m!M7UbZZMk&JzPeIN`;4mz4732iLVOZoV$k`YihGouzoK3)a z$b)*iDL4;%z}XC(M?Bzc4vzc1Su}fc`{)+nFzooCpq&1;B{&RQUJ7zXgX4Ou5~bv6 zf5w2rFkw$gIpa1KoF{n88#?}d&IXJh2M)us4NXr@4(3=(8P;G5awdSoFzsECGZ7qy ziR^-$N#HOncNFAI28Uq@qaddi9EN55f}A>V7}n$qa_Yfh*w9gsGX)&?XKN@X=UJig zQ^9e6R;Vy%8aNE6X(=eD?@R~hMGt&*1~?4IaA|rlN!r)1XM)3URJ0&xD{x-nja$z2 zYz@wflxH4>{QA{? z)VGV1pKz>YJN~#}X<`s2{=yEnEsx*cUve6l#Ug@m+R5nJCG_`{i_g9#BErmjHM<=x>@4-8 zsGa;Ms?m?4cJ`yFWquUZs68NxVbYOT)SeKByx$jT4`7u)Mh?M)s$;fJYb>ekv|cbdekGXi3o~t3`;G4r39U7qcIGOi^8@yD4z!YYzNdo4Kx#E(UZ_VRkDNKk1S z?Zm^7#!uMOSWTkZr+yqRlE~E}k3cFvWlg2(TUL?&uDyC3$)3nU#G{bL&)CygP19(v zAV&lFxh&FNUXKxpt0k3oPbc1zB7?fo_NsCcpkGTv?IrhQKritcdJ3T5utSGg!`r9AoGMa~3Qy zh4wmf0f;||676O6LJ-gOl=wRke`Y7*$wOnZtZ(hONTlSf7I`sJc_C{ma~~+X1f26d z;9LsM1s-rNW6!GGal70j&J`YUuH@(7Kv}82As?rQj0?olw}f2fCG2W`SgBfwNG7$G z&R|z~)oAOh%Hyx`8h9-~aF8{H*5|6nU*|ROdVb&wsv8!?LpZT;0*kph^6`e>0(v7q z^z^CGmfrFkFmL5&s>5N{blNiXHng^v^0u}G znYV*^885RO%X3Z1cqrPAD;8CD+LHNuATRe8c?W-PR@zf(YftX<9(fl(GK>~wPOPgL zZ%E~+n1ApZ_(y)=6l)4?W%X{afq&u$&c-6gGMqJw;Yp4hnfPbViTCgm$J^3q%f`QW zPW&rO?h0O$TZoc656=&@_vXsRd-;>Ndf?xX!Yg@GxS2JDwoJT_KZUCY-p>!*1LbsX zs4Wz2$ypa4@SOM{a`8sqT-?T%Mq4&M#Gl3j#E1Ebxc;PSR(m)SPUftPk9bb}JM!@c z-h5P7KhQMV^6^prG!`KKgP$0O<21&#UUj{7-#LNDyaqnb4_s_bp)C`i@EZ6eKkzub z;oX$#R`2rUo7{QoH=#bwPeuFDC#|+DeFjc&6|WPl0rj7tUd>CzT+c8y*E69zm{8f9 zqk=xmAK^8i{)?ZA*`7XWwdLz`zX|o<{8Y^MRH+$eb^TI~JpGU7#OL{ms((P!Xv@nN zJSV=$PrNMcMH7SSO|;yDl9zrH>dXApj00NIYRl7C;DFchI^Y^mUj_AgUh37Dr$l=? zF_=ikC+4c5uYq}u$IREkyq1@Vo6KzKwB_g<{N<=1^G$x{omfdozY-WqBywixTfYVL zZGPyHeN$@7(sxjdZs9FPg`w~ArxbZQT(6_?{1bccA+5LZrgcrQ?*n@)FLqU~)Y@|P z1O6bd3HC#N>?4LRwT9YqR@aYy1Lnv4%%l6H)0U;5{07WV`I#6|)TnVxD{g!nm#aPf z>^ES3&dW@3+7{GkRpUgtYulbRvoz_&^GLz{M zbQ;(~$Zz>miMm;}4EI*%8H=iy!T!hFmK7j=&rd|Xw4~9Nk^l9a_ya%jQCx(P?&IdD zkUyeFauaVZu9o)`csKL%FlR#2XUoB#`Qy1--Y@(-?9HoMn1ZS1o)h}jbD{z=*L>{G zv!v0QixUE#6HEAsYHn{2tt%GSa&5Al(9d%s(>vC*nL5OlMq54(;7?-#;y@OnoJlEgFWI5@rYCE5vR-} zPPs>%3XeFI9&xHX;#7OYsqu&-@T750-x=mndWL(%8Q~FUq(>a4$A)PGl(QVHrb^h%?3`&RCB)<2>Sw_lPsWBhEyRIFmf$O!kOV>k+5UBTl_X zoGBi0rh3Gg<`HMQN1PcRab|kN*~%l%)*f-T@rbjnN1W|E;%x5`r@tyq zh%?tC&ODDe^F87$@QAa}BhDg^I6HX6+0i4;VvjgWJmM_%h_jPNoJNm0Opk^ZoKIZl zQF@v@;w<-wvx`TZT|MHg@QAaUN1WX~;_TrOXHSnfdwIm!+au0C9&z^dh_jzZ9L*z6 zvqzj3k2tL!aY7z(+C1X4d&KGRh!ggRv%g22PLDVdk2qZ(aiSh^Vjgk2J>neT5hv~u zC*ct%=@BR85oe`GoF0!jt3)_MXUAfl!B|_cGaBoOgj(A}#qV_Q6;Y<|x6g>tFW{bV zphuj8JmMVe5$6z(IEQ+~Im{!@;nSwj~@v_zsg z66;ZCE1fAj0RdxpfmJ}+i99hQIhpg}kO}JLH|CdFb5=F=*cz~YfGI8!&`7=0rq%Rr{fihgq#Kp&&8FM z6x3w~C)WoRF#3h7_$qW=iI5f`qso8e1&l+oV}WfkfYr~^`=U_|Tc&eb=dKX3W0i@FS1 z#PD7yscNe0T7maMaVKCR*cOZD?66pb(0|cIUCt9Jo2c@pmblg(?ACCLbMb+?0)b+9 zLe{3KbfFfG2U{Xx2U&L|6!E^Kn|su86*#<4k>=)H&6Df2H0A82UxV~8JRY0P$-$T7 zUEb;ivDQRSEZ!PSbZGHb!<0-{E__wVwLIx(*L9swbzP5qWO&S#Jzh63#j9ccOi?aB z;5Li`&GOY8(OMm>58Q}wF}&|v!Yu11rf8Yfo~bt8>{DI0ps!b9x{7g79h0zuMl%Gt zKd7Iv0K${O)I zL?WJ3?398*@0-M8;(jOMUd3R`8}_TW zeHDgdcZ4I!;s-i^^rNV|MMRZnT*_IX{^UbVe-_bHl{FS;p}NPPvi>3>tHQ7ZXR-RL zA4T2EEvo-Br=!7SxA@`1z7!@tl^?4GL?~V4^#u<(w>?pYONp1HXs4BE~K35kzcg zHe!(Z6a7Y_%J8k0z7<)0R^O%yd`^#^)ra)gQq28TnLRG7&xg?G8fhCltL5=f(&QR* zfi#HbXFY&_X9}R9G7!kAGq-n`Qt-FU&$_Hf5szVJGAbeR2lD+Tetu(o&%pO&d~b_y z_4jAuTYc#>ZRrA6lm0J(z(@P3HwQD54=o*7%;}D3Nlb68V+&#S|C#*4Vf+VE7#j-f zEjE;oA(X?-q^MB-NWQ;Ad`93qi0^gry$QZY<6DJSeUVTG_5Xl>JlIG=sYfx<8IGeg z+C!QBeDa16c&;G2ZfKny4!t(N@K^E26kmkg|(DU{9j|7FsRK+eK%?(?Mw@=2~h7F3%% zj`@{6g*c8hQ(}+f$I$%)lC%Kd+v8i^eDX8?R59&`ZxY8K{#AVq7>&|bm5E~#8vsK| ztcwiSgO~|n=D(WM!tr~WD}F&;V?LF|2JaaJZ}O z%wIJB&@1M@xPry3>p8@0?LP6+wL|eQN;UpPwNOLxJxu=}>3=AD)4%kRmEF;x`T`Ym z<63(xs`lclk~LJXl&$2ynM9*X=FA;Ea{p$E+-eo(F8ldK{f9}E5kvJS+-ygE3y5v& zNuOBJzhr*aWA(g0WxaqxwvL$$7xrbQc6J9!RZ}vgbzvPQnT()or?s`^s+Jp?f5-Mg z57>)L0c$XSIhV4R5VLhzV|FWEbQ-p#H<1jt;N4ply1YR%T7NTDnGd7&puNl|^t|RXJ+J#r&l^6|^QO=Ayv3u3`ij1zuRRGPm(i|}e@1v^8%!DI|;_xcDd({QAj?}#v~n$*#$_INDTrQjCgyYTw;7`&c@ zwG6LY&56V8wFr_J#spEG{yq6S*^j!-zb6uh>fJ*<(O65Tt&@`u{re(Fy>n*?_y->gD8MfY^AtQ z|8Jte)7WS%*IM2Y3bx0?UBP%roiN1U*;(&v;y}bf3OCIjUQpbetpvF73@8YT{c zrX6@SshBO+63JjsI1<5ejn<$#%ByvSqDI{?c?El{6Y&$JJDNYw<$&c2Q(f2u)f|hO zhm1`|VNF9sT&ub%6kn-D)!CdxXDSjYx<4?nR76+lLJXAK)h$28D=HIFRMXVbi$~-2 z^O)VCsDnel6U#+}m8OeTsMkS7tPoK&YRi?ea7#$tK-h}e&n;Dx5spQR&q>t0vW?6- z!c`Azly_x(HP{i0IcQv}AS~!dVbwetTGCKqHhgOH(u$67675l}If4UET^f2i#ZE3y ztl?2+%EAYD{*VRnRCAlX12mL}RnlPWHB1j+_t2u@;_pD4IE+ON?H>h3VBb??L_=nv zj&-GQO1!%xWG+{xdx67Qu<4ChnW!V+Rr5M=6;h%jm5P#84YMi~ZpTK5$f{nd#bfO; z7rI8u=vtUMQ0t1dYg)Y7S-f!ZR_P)$zK%VR79W+3YJXFEEZ(l+8WMAH%ET1T#pL33?%vXQ z?m4#7Qr(1`aXLa;E4qoQ`&zvFuLoUo*>%-4MbQ?ug+i+T>P#56B9_i!7goI_mPlX~ zHtJZ{20V6TD=<~dLapd1Cakj3?Zt*XL|f4rmR<$d2R&A*8T7(tvk|{EESC>$#MLTY zS|?7;H;3Z!u#-_)kY5?v_ENpS)fP@*)0n3i(69x*c>n3jI3DkOn-NemdQnPsT6wA`3%fr#uP`o*| zs`yg21%DV&?nlnXj+p~P#yUAR_y{>D{prEaQ7=(yBPV^U{r!A30!Cp+R{_ii`c-|}!&y>wfKl4p(HTDhX%tPEZv&F^yP}4qtdsnNch%b)3+Vsw+%!5 ztnJ3|Whv}oaogZ-&lEceZ4FG?tgbhrPuv_8x>Xu5YOUAIL z!fEVw^0m4~X>}_@ajnH^>~{9Gx@98jhA;03<9QJEj$fDlIGrQAYx1$SyTQLogKkLWNrhur;oMmC8BK@HhH-Y+ur__wvULk%*vSC;eBowtXV|Zutm|fa8%uY^|%lw23u2DcN#?f)M{c3?GUSBBLbYa}@p8XyVMPhHD<128WYKU zcA4EGGB;XXG#wSt1OA`rjPvNUt@mzf#FN!Y6aSC2CQ<8~*lYb}1>@?du%;k(+5Z=@ zDi7kM;9lK1@9Tw3-g^_*_V{yQSMu2B*rAq}@E;Qe^> z%vbSSbUU3yD>P1@*-uf`zh6EWszb;0w*6CjY|wI4BoCqiJJH?IhDwIB6*O@0Cq z+`tk+OW$!dVI;pJ`%w?$NldMC_SdR>=2m$!V%hh-HNq_XF^5O4m0s&#~FN;7F`ShL2l*>gYuKMdJf|oy$otH{@~(lrFWt+tmAnzl z@ETa6Bcxt`a52AnEyR7{S8>-t+)wVs*~;C}rfxj5Lbrt$KP`Pdl>O*SWj8?CFYcAu zO55-zbq%_DYt>C*yAj%c_Nlg;pzVA2+Q`sW-Phmu#_F4KU+pIBoHE>4-4kq&IC`Gu z7D(FEUy^Qxq|IDQqElV^>j^4K&6<;EUv5M3c+0gQS~`@V(nWYSB+(O#J9v}hb|`wA zT~Wn~FxJ~~X;e~cYb)Lte-Babu#3X$IFU#%ZanWPUeO&;^e($1b%Ov-W`yy+Q37R3 zy>rx|C5u;gC)B;hu5MW7Ru@;&?t-+}*`*Dg+oQ$X+y(6qQ1%A9vSE$kxQ16>Tm|iq zkoG3KH1!^VUdEEKZfE7}Zs_{Ry)J5~YUXya{?KO^USSWnxH;SPC&>HQzw-VJd7m)I zYsmUPqBnA`I@;wvc=tfvr@mG97pQxWL0vX<-4XL$y7Y#Ow+y4^nrU#BuFYs<+E{R1}+&<5AbE$ru5qve*^AA_c+@i zp(gF^Em|~+%f%h-3AhjQa8vv!?0$4eH)jyG{TF5)q0cumyTP#sZBIOy#FOJ%@!g0A z5Vm>-b*47`0kDcD+_ZvzSBq*Fix>4E&!Rffq{?*Jd_3D|k(4gd4QrAuZnG;6ao}5QwSlfvA}kPlnpGmL!z5gu>lc zKlwXPG;G1IPDevM#fSHp%;##-8Sp5QIfFf!t4039`&2%LR8IFcm5+O$$|sP@t-MX; zlisKDDWr0yx2b&E`&2&TJ@TIj?09bj`>gk={1;L=&f8Qz=Y1;wjZ{wbHkJSJK9$cS zl@q*89mmv9#D_88p6%q}H(~waVnudGK^fUlU2`&@8bf+!ao0aZ55^M>1>K zlR38yPV>2<%r zLkA3MaF|Um&JBj6&9T;AOWwZClV01XZMd1)I?`EZXK<@ z&y!eE3i>^VBNNzG5DzDVn8u06;?3%^+U{7&$q4=fgk?+jVG+~!A%FZa)jb#lQkFL+z<5lr9>SxB^T4K#{&1uNK@UOHlk(=wW=4Q$L zSR&0t4gGkvERyVS8o967BgZQ5Yea8-*68&+0cCD^M=T!g#hN_cy>}Y9Z`dQpChc29 zZ3EV*4Y@5DsF)qX>S)|)yuM?P7lWwp5vdJXBUODj*57u-VoTS=U<~1kU>BCtko}iE zWSr`LKcRMX!zp}@RLDc^cDTb}TWL&43@L?-7>>G&1lQB#fI*iw( z0DHXHq#?Pb2e6i}sy)#ApC3dW?fp;c#~v~sUH#d0RrEtYc2;w5B7yz?-D-P+ z8m2pQvPlE{EpH&ZyvhX_r5n+Uq~e{9duEdc`B~gxc5&6qQSNq$hFdTOP#e-Ckudg^ zIV^WWU?Wjh8)21K3VAVBd1Vb~MVDyp-C@UdwG5)V{U)lMy`1&yMtd+f9@1LdVm(fW znUgAfs;iP+m+EPcXi8?D6T>9C<0-UBRely%jpEUbD6X@qZ;jbv9= zdIn;)tJa0u8FDi&80A}KYq2XEJQH(h89z&)o7U~3gSNJ>)vd#>uKysEw^{KPb#$s3 zd%d0d+Pc0~wjSEgIB)xjp4xDuymjMoNykxJpFL`-7s;b-19okdBlYJcW7vez8jOTO zEl!uACT-|rZ5y#`8+2vX-#WEOcZZ9KkD#yBZOpE&^cBoaE{sJ(IJm8~ViTiNgWJTv z(l%w6Hsq&dpk`L2IUJ1zaqP-z%r^6@u+7JIm)MPTXt=u=Adpi zYAv0?UfhoE(9E{;r>yPSWvRUiBbvG=23v5ct%k>$a4n+4m^JuW+$_}3V_EB`8M$^m z1K}=mv*jY^Ufdi+?l|_yVZMH1Frltoa#Q5yiUf{F+dRbWc$v5zsCBuE+kCmWxz@G- zaXW!MZo0>HhobH70=E$2j_|9vMIyP1Rc6&X#I;rKLbn4#cO-l0xYX?^5X#0 z<|tZ?kaep6WbMpewp6q#)#Ekk`sgTF%Mh&7*n{O(Rg+At@JNlDSS?4aPUnx6OI5pw z#Hy+>+q-a7uy*yQtQE+^GuZR6dJgGPI11Np&~>5@b?q*ajg`3M*iB8`1JOE(Jz8#6 z?a3Z1H3d2pPXws5KFOHoc+PAu#Oq|%cyTG)n>}WMvSeW3{7Bfm=haMi!Ml?t?SnX- zV*b42i-gF@N;hiu6^Tvx>`*8f!Pew((zr#*OmX1|?dL;H8jGe9nrpc6YWRY=jTdoy*0Hlq&C5u+BLy;1A8bh7MH;G&D%O_Y7gKK@|s}dNbMoKsa>2a zwYApNCiqjkCfFoWd${Y=T6%?O7iviRiKl5o#%;7FsBjlL#S#zNBUD$`_IV+4(nc@~;VqML}cLxuFqJw;;=unozWY4j(1+hqL5Z7kM zja%Q%L>IQk!w{qc&7XH{S=6&|=TP~AcnFWYIn;DGf0+7X-DXZa+?Bv#28XJSU{Pf$ z;HYku#{EJLRUIj!YUpwem))zw&DcBWP}xx|$}EL_03H#W9ZBJ$X=jD~XckF*vJfvC zYHC-iLsiGfsA}p6x2Xel4pkj1qzW&%N1dxWPDa)87#?hMuIhLZRaHARC-9aC9w>I0 zg(u+UL)J_BOOC_|6EfDq1^tmoi`H$-{iVmzCj!fG34V4Qtz$=JhwmnKbkaKRISFKj zJLI!u{V~O=EV&~VGln1Ow4ThL^J`-PYEC4i-lxu7H{@u{bBb@3oyxBa4e8JYXx?$( zw40cn=38Z_vnaDPro%9_&RodRgA(R&Xnh8cFk6-DJG4I2BhFbKanAOLa}EoKW=~+P z?29RS!O6^~rlM_DH$L~bL!ZZjwp5pO)XuN8xg^m0Y;~}bdp?UQOJ1k}`+`tQXUsB_ zeSru_-(uDp!|BK%E?cw!FBAb*V4oIt`E&+5w4P22?{_>rI%-v8F|`jeyb8B>bgNsL zi(b#Xh)ETBX&R~qt9@aqaI| zGf&u#-qI1lTCJt_U&F(+)&A<)T61r(U4K(byngyY7{lAt+mn{|*x~{m^(h`dq z?w=lYUMIpE(sV#79FK>rP5bpctdfR_X7bR83|bP4W*=lNBGflvh|locnW+UIRGSt> zWv~>r8{q}}n7SN$fsIo-r$g()Yl^n~ya|coc#e#89MD1!o)yKe!?=Tv>di>}UhWfL z!lmaH#E1U@?V*|Y1V^j+wf0b}b@*~CpbT%5mo(u1`pg%0r&TNtABfRIFwvPUab~0o zJI!rewUJZQ?T8fro7kghr1Ip^??GmGbG)R%$e}>V>6l8IAJUR)I=BND%eTfu2?tf^ z4z7%1S9T|2#{cg1dVOM+C(G^vp5bY6fh_w2TyI~qE;_b+0cC$g%=jO+UgIpg8+eB2 z!vzBKC*RhxKO<)K?hBJy`|$z&lr_$>dw`$f9xsq(e?cwNe5vfOh}l&4%A97|y}(a% zj~B?Yzwy*E5*XFW4%)P~uCA{BfWV<8Tcw9J_px(Sok?@15YGLAoT-HKfFNfY;XEkF znNBzl336r-&clM7nS}F*9H)LV;rv~YQ%g9H3UcZQ=O2O`>IaVra;P6XF36#N@Pr_T z`oWWe9O?&8338|(JT1tfe(;POXA1R$e+qJ_A3Q6_p?>f$K@RnU=L9*_5B@F4p?>fm zK@RnU=LI>`4_*-DP(OH4kVF08B{|Mi>IW|ia;P7?BFLeB@Twq(`oU|09O?(J3v#F* zydlV;e(g%Acy+Fmx3JX2VV(ts2_YS z$f17ljUb2m!MB1O>IdHma;P7CFUX;O@LxI34C)6z2y&<&{3ytwe(;kZhx)<l6y zP(KLDaq6faY%Iv3ez1ukhx);$f*k4xn+bBLA8anjp?|fZelSsxL;YZqAcy+FWI+z~gIYlj^@BP=4)ue2 zK@RnUDRLaTKXLL@K@RnUX@VT;2h#;P)DLC|a;P876y#7p*h-K?{a|ZB4)ud=1Ub|X zwiV=1KiE!=L-!|6-d>PH{h&dRL;YZuAcy+FY(Wn7gE@j6>IZWLIn)p4338|(%opTP zKUg5hp?{h(2h zL;YZ9K@RnUWpW(4KXG!CAcy+FazPIDgIxqU)DLzQ?z2hez2Duhwe|Dytg2S`oTVe9O?)A3Ua6)>?g>fexM0*s2?;7a;P7)2y&<& zvIY##4)ufm1v%6YIt4k@4IYqd9O?&A zK@RnUm>`GxLAMxZie-P|0fHRr2XR3T^@D^Uhx$QMkVE|-CCH(Euu_mi{h&vXL;YZt zAcy)vuONr|!GUrd%l^cJ1Ub|X4i@B4KR85?L;c`TK@RnU!vs0h4-OaPP(L_AkVF08 zNI?$ugQEmF)DMo9<5>159wW%1esHWHhx);Bf*k4x#|v_(ADke_p?+|pAcy+FNrD{e z2PX@1s2`jn$f164svO6%Kk+m{4)uf61v%6Y&Jg5KKR8p6L;c_^K@RnUvjsWS56%(f zP(L_VkVF08JV6figY)G$mi>tr2y&<&Tqww)e(*a%4)ueJ1Ub|XE*9iaKe$AYL;c`V zK@RnU%LF;p4=xwvP(Qdrj$_%Mc%>kR`oUF#9O?&G3v#F*TqDS#esHZIhx);Ff*k4x z*9&r}AKW0wp?+|qAcy+FO>!K|{=}OFIn)ns5#&%mxK)rt{opo14)ufE1v%6YelN(O zesG5%hx);tf*k4xcL{Q+AN)a%W7(hhM?nttgS!Pe)DQk7$f17lXF(42gL?!y)DQk5 z$f17lS3wT-gL?%z)DQk9$f164pB%@sKkhFAcy+FKLk0{4<3`_SoSAAF36#N@Pr_T`oWWe9O?&8338|(JT1tf ze(;PShx)-k1v%6Yo)zR!Klqm*hx);DVw|a#{fYk;IW|fa;P7? zD9E9H@RA^h`oYVB9O?(J2y&<&yei0{e(;(c$Fe{1bwLjGgEs^@)DPYiIa_+a;P7CCdi?F@VOv|`oR~19O?&O3Ua6)d?m-R>`(k!kVF088$k~BgKq^n)DONB zIXjva;P8tD9E9H@RJ~i`oYg~9LxU1Uj#YS4}KNoP(M%@nfHID zP9c6!8xZ6WKd3DcQ6ygW90|hz64{8U= zap?ZU+QEVx;s>=u1UbYHYD)z<)DOx8In)oz1v%6YDg-&y4=M#Y)DNooIb|6?m`eSi znxC_c!J&RoBgdip6KjX^b6PX$nMVC!m>`Gx!Ek;~*hmlcgAsxp>IWnFIYxP#M*U!v zAcy+FT7n$v2W#_l%yLcrU>!jY^@DZ!Id^8tHQk?ByPhD2`oa4AoB>99s2^+~$f16) zp&*C)!A61{>IXqV4)ueL1v%6YHWB1dKiE`|L;YYgIS$>QSi3nt=g>?&ok9Iz3qcO` zgDv?vKO5561Iz`WbPfelS6hL;YZ)9Ea{t zteqstp?)x#pJViUXHq|?<>wgnXD0Q7IzbNggL**@^@Ayb9O?&C1v%6YrU`PWA50hI zP(PR<#+gRIb{>b29rWrqTV0wJZ2JM*h@OKiG|* zW2T4t!S4K=%sj=kdg=#z@N>+#Q9syIkVE}oFF_9VgS`bg)DQL%IX4F4)ud>K@RnU0|YtL58~{c%KI`t zYT2Kd5adulNV0QE#%I!F*`Jsa7jmbkRXTp!NJ^|Kp?X}aoRN6KR85?L;c`TcFt0xpJ>^ic$grE`oZDc zoRZ16Wy;%h>IX*%a;P61DafIIaFig2`oYnH9O?(h2y&<&94pA7esG*1hx)FJ zjhXsm*`IiVAcy+FiR_$!F+QC^{oo`)4)ueR1v%6YP7&l#KR8v8L;c`1K@RnU(*-%y z56%$eP(L_Rj$_%Mc$OfC`oY2y&<&oGZwoesG>3hx)f?K@RnUi^Mq7E&CHM7UWPrxI~ac{oqnT4)ue}1Ub|XE*IobKe$4WL;c`NK@RnU zs{}dJ53UyEP(Qdvj$_%Mc Z`oVRA9O?(x3v#F*+#txIesH58hx)-yf*k4xHw$v8 zAKW6yp?+|yAcy+FZE_sT{>0k_In)n+FUX;OaEBm=`oW!o9O?&m338|({6UaI{os#+ z9O?&m3v#F*{7H~Q{ov1X9LxU1djvVu5B?&^p?>gJK@RnUdj&bv5B?^|p?+|mAcy+F z{em3o2M-8xs2@Bi$f17lkQ~RdKk;Ef4)uda1Ub|X{w~O&e(Ulm`oZ&p9O?%z$Z;(D6JHeMP(OG{kVF08WkC-0gI5GO)DK=2hK zAcy+FckG%#XL;c_JTw;So9e(;MRhx);<>>RT_ok{&bVR+yF$#RGv)CJf%rLUy(X9nG$SXUy*A%0NT zkDW8(S0g>7e^A$7kVE{SZU8%HWV4YT;sISiMviU>&pl+}rhxkF=5O&Vs z4~+aFeo$8`$f15v#?GnveI`A0e_~y^Acy)v1wZEkBR$j)Dg`;z531NXM!PVhmij@p zAcy)v4LhgeFe87c9}E@bP(K*P&dKHv^@HJp9O?%n*g2*9X7Y#bPplg$$f15Pik)M& zKXud())M4UKUkZc({CLkf2bdYhcey|=pr(~|dp?;n4kwbz}HBCmI~;2V>befq=oGelSjuL;YYpKPPkl&WtJ44<_()GWSNz zp!X-%O%&u%KbR!Qp?)w~kVE~TR**yeppKuDx&L|w-Je)jFUX;OFomCElIX9gIn)of668=n*jkW7{a_n84&9$vx2+(D`oVUB9O?(# z3v#F*GzfC2AIuWuP(PT>&e4qV!Zhj!a|Aim59SJTs2|J|Ib_Ba;P8dD#)RJu!5a4_!*;IQ$N^EkVE}o zcR>#IgFWOpbbn&qo`M|e2YU%}s2}Vt$f16)4?AbA?o7F!N&R47K@RnU{RBDG4>Umz z^@C@%l^a=J7@Uu>2f`DGWCNtK@RnUc0msHgAPFs^@Fe=hx)<( zf*k4xoq`R$f14^mE&0UC&mOh)DOA^In)mh5adulhzoM4A0*g0 zYi0I1%&et;kQC%lKS&93s2{8p`&|!PdrGF zL;c`jK@RnULj*b04-OUNP(L_KkVF08a6u0BgCp2EKb@ZGhtyF&I8u&d*`IinAcy+F z(SjV>KR8B^L;c`bK@RnU;{-X>4~}Q&81sBH>!}}{AjqM9aH1fG`oT$p9O?%r%W*9G z6HgK3P(L_TkVF08G15o+rqmesI1Zhx)+`L;c_)K@RnUi`h9N56<}YRO$zp z2y&<&Tq?++esGx_$Fe{1azPIDgDV6%)DNx{qG-cLl_s2@Bb$f5m%Cj~jw51wM@ zWX^#hZqyH+7UWPrct(&z{otSMoXou*h#U2TXZbnl`vU4GTlOdZi=UJ3kAg$};5mMd znI7r~|K{hI>7jn`AAXLR9_k0r^K;DfP(OHqpJS$n`oW9*95X%C4_@NunCYQ@@G?Kg zOb_*gSNJ(bdMx`BU*+c*>8YcB@ESkIOb_*g*ZDbSdZ-_~!Ot<%L;c`QevX+Q>IZM} zbIkNmKX{v;W2T4t!8`mMGd7jn`9zVxOk7a-2`}`auJ@wQNKH%q=>7jn` zAwS1V5A}nO_&H{Js2_aH&oR?O{ooURj+q|n2cPnD>N4Xw%l(O;@pH`dP(S#bpJS$n z`oS0c93wrJ{fS=+a;P7CCCH(E@HIcj$REr8#BcaHX8uq=_?Dkzric2$cl;bPJ=71r z=jWK|q5XsZ@^j4eP(S!VkVF08M>&pVf8tMq9O?%@^K*=HV7Wi>7k-XW4yMxn!LR%r zGd=E2!5DVVz{`yEP(K(e$f15Pj-4|jeeZGo zHfs}3GAG}qv`yqrS~V+PZZ=(KbXYN`O!!Z^@GWR9O?(P?3~Pg^7`7z z)DP+eIn)p8`8k>XXzgU`2U7$&)DNZ#a;P6nljG3+iS^S3IkbN;Ly$xLV5T64`oUI$ z9O?&K3v#F*Y$M2_ey}Y+Cw)%5zP6V7!FGZi>Id5ka;P6P$Z_cY#QIr+9O?(N1v%6Y z<_L1AAIufxP(PR_$f15PUywunV1Xcq`oTg$4)udYf*k4xJIHb9{>1tn1v%6Y77KEy zA1o2%P(N5I$f16)lOTusL8Bmt`oYeE9O?(l1Ub|Xnglu250=Yu=>Ej|T?9GQ4|Wyg zP(N59$f16)n;?h!!R~?_>IZuWa;P8d$<8tMSJh6Tey|rmr+;Q1X$tj&y#+bc5BA~b zWY%SB>Hfs}efc>?+@?}L*zf;pyYF`|#{iDw=S0XJ*?X^0-|yXN*n982wYP?pN=qpf zDw-%24TPdXAv9>Jw4|gY4WlHR3s>K#>wW)zzyE^Q)#;q~xu5UzInN_H>_IirVGpW{ z4tr2Tbl8KMqQf545*_xSw&<`2bwr0fsGHu&dJyds>xm9~P+xS|g9gc+?EYMZ684~B zawofQR)Nn?%r{EzWcRZwl&}Z)CwI2rXX5*v^NrIx+5Mjq_Mk~}XZwAb684~JdMCSI zQoo zbl8KZMTb4;BRcFsU(sO?`iTyE&|h@eg8`z$9t;#6_TU-MiS~)liVl15oanFzgG7ft z7%V#M!SkZS9t;s3_F$;!um{6Lhdp>fbl8IzJtx{H4i_EvV1($f2O~v?Js2fA?7>T- z!yddWI_$wKqQf4H79IBBRncJ&Uh|x2pZL1yum@vAhdmf8I_$w4qQf4%DLU-Ixb#k8 zhmzy_o%7?Ic`J%%f zED#;`;4{%-4;H3(vg@B{pSVbL*n`EM6YUe1hz@(ORCL&b&qaslgJq(_^T8LQ!yYV8 z?_}5YmDqz7$(`a}gqQmpSI?>_v!us@1b`Gh`9&AYM6qhMHXD%1*6E}(u z&j;V7c8aq1UgoN>2j7bhd+>wkum?Yi4tuakbl8K7Al& z=iYpI_TX>PVGsWCoM@l;Ka=?VKiSuf?{^MGqJtiUV$nelLMhQf55f+jgC2w(MF%|y zr9}rl2s?=mdJuLN9rPgV;yK(WhBBgq^Fi2Ebl8L4M29`tU3A!kJw%5+*i&@agS|wD zJ=j}x*n@pUhdtQWbGT0o`-u*FP*!xsOF{qx)> zhC@V$JvcPIQ+WS&KF=N;<~iIahQmdNJvc&i*n=ZQhdnq-bl8KVMTb2&Ms(PNV?~GO zgX2VpJvd%;*n<;1hx^2EqUf*(Cy5SwaI)yI2d9V*dvL1gum|Nthdnq=bl8K_lRL%b z3ZExdWDm{|9rmES=Ww4GDu@nykP{vDATK)XK}hf1Q}}-4J~31j9rmC^bl8JRqQf3k z79IAWis-NhRg*hK*>wr`iQ!DqVGqs{9roaC(P0nH5gncn&J`W@;5^Y`56%}I_TU21 zVGk}$?_~GqDzOI_c@Fo9;bPHY4=xcM_TW;{VGk}79roaI(P0m+5FPg5O3`5tt`Z&g z;A+uf53cbX?i0hcqQf3sCpzrG^`gTb+#ova!HuHB9^524?7_{V!yeosI_$x%qQf5C zra6K8#BjUlum^XD4tsECawq#7DU@dq?n>@tf8Q9mPYidbcM9jx!1p_cdqjslxL0)8 zgZo5>J*Xx+>_K(U;XX0c5FPfQrs%K-wM2(Ks4Y4?AJh>Yo)7AZ4tr2fbl8LXqQf3E z5FPfQq31;V#73gS9^5ZF>_KDEVGo*!4tvm4bl8JtqQf3MAUf8r!yY^=I_yE)^iJXLz(TZ7Y$rPG zL3`0*4?1{Gv`_3PI_$wC>7DF4IIstuM29`-EIRDLqoTtebV=`IKc|5`=qfrqA3P>H z>_NBm&i3zX_Mp4xMEk@ZqQf3ME;{VN6QaW&JSjTtK~K?P4|<6Xd(c~S*n_7;hdp>& zbl8JFo)hg8`-%>G&`)&OgZ}BA?Drg>pBM&+4tp>#y_5a^;rpG#Gor&DJS#fv!E>U+ z9t;v4_F%B*MEk_&MTb2YB0B8BP|@M}V3_Ez2QP>Y&j&Ax4tp?Mbl8ItqQf4H6dm?p zl;=eI#Fs>eJ$PAk*n?L@hdmfAI_$x#qQf4%COYiF>!QORj1e97V65n{2XA;zv`>6f zbl8J&qQf4H7ajIsg6OaZZ;1|j@V4l%2k(dudoWRS*n>%;!yde=ITfRQ;(MaQ9=tC) z?7?KwVGlkK9roZu(P0lh5*_wnis-NhQ$>e8m?k>x!F11w_K6>h4twy4=&%PfM29_? zDLU-IEYV>PW{VDcFh_LQgSn!^9(*b~?7=+GiS~)}MTb3DAUf>9XQIO%EEFB~V3Fvs z2a83AJy;?-?7>peVGlkR9rj?E=S2I&FGPnuSS~v3!3xn~555!~_F$#xum@j>4tual zbl8K{qQf41EjsMM8qbOLiQkA0d$3k?*n@9Hhdo#)I_$xE(P0lZhz@(OQFPdY??i_^ z_+E6_gC9I6+9&=fI_$wF(P0lZiw=A6ljyJqTSSLF_*rz=gRP>&9{eIYg&zD1$oT|e literal 0 HcmV?d00001 diff --git a/data/itemdefs/untradeable.txt b/data/itemdefs/untradeable.txt new file mode 100644 index 0000000..c50805c --- /dev/null +++ b/data/itemdefs/untradeable.txt @@ -0,0 +1 @@ +//untradeable item ids in here, one per line \ No newline at end of file diff --git a/data/landscapekeys.bin b/data/landscapekeys.bin new file mode 100644 index 0000000000000000000000000000000000000000..e1ef6e6b62248bd159715f7c978196ba80e8f1d3 GIT binary patch literal 23160 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 literal 0 HcmV?d00001 diff --git a/settings.ini b/settings.ini new file mode 100644 index 0000000..f0e54b8 --- /dev/null +++ b/settings.ini @@ -0,0 +1,28 @@ +# Debug/verbose/local mode. +# Put to false for all debug and startup information. +VERBOSE_MODE: false + +# The servers running port. +# Keep at 43594 for obvious reaasons. +SERVER_PORT: 43594 + +# The servers name obviously. +SERVER_NAME: Osiris + +# The EXP rate. +EXP_RATE: 50 + +# Use SQLite for saving/loading? +SQLITE_SAVING: true + +# The servers SQL information. +# Only needed if SQLite is set to false. +SERVER_HOST: localhost +SERVER_DATABASE: database +SERVER_USERNAME: root +SERVER_PASSWORD: admin + +# Starting position cordinates. +START_X: 3082 +START_Y: 3419 +START_Z: 0 diff --git a/src/osiris/Main.java b/src/osiris/Main.java new file mode 100644 index 0000000..52b9642 --- /dev/null +++ b/src/osiris/Main.java @@ -0,0 +1,264 @@ +package osiris; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import static osiris.util.Settings.LANDSCAPE_KEYS_FILE; +import static osiris.util.Settings.MAX_NPCS; +import static osiris.util.Settings.MAX_PLAYERS; +import static osiris.util.Settings.PORT; +import static osiris.util.Settings.SCRIPTS_DIRECTORY; + +import java.io.File; +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.Hashtable; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import org.jboss.netty.bootstrap.ServerBootstrap; +import org.jboss.netty.channel.ChannelFactory; +import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory; + +import osiris.data.SettingsLoader; +import osiris.data.parser.ParserLookup; +import osiris.data.parser.impl.PositionChangeParser.PositionChangeInfo; +import osiris.data.sql.SqlManager; +import osiris.game.model.CharacterGroup; +import osiris.game.model.Npc; +import osiris.game.model.Player; +import osiris.game.model.def.ItemDef; +import osiris.game.model.def.NpcCombatDef; +import osiris.game.model.dialogues.Dialogue; +import osiris.game.model.ground.GroundManager; +import osiris.game.model.magic.MagicManager; +import osiris.net.OsirisPipelineFactory; +import osiris.util.ConsoleProxy; +import osiris.util.LandscapeKeys; +import osiris.util.RubyInterpreter; +import osiris.util.Settings; + +/** + * The main class. + * + * @author Blake + * @author samuraiblood2 + * @author Boomer + * + */ +public class Main { + /** + * The Constant players. + */ + private static final CharacterGroup players = new CharacterGroup(MAX_PLAYERS); + + /** + * The Constant npcs. + */ + private static final CharacterGroup npcs = new CharacterGroup(MAX_NPCS); + + /** The Constant quickChatMessages. */ + private static final Hashtable quickChatMessages = new Hashtable(); + + /** The local. */ + private static boolean local; + + /** The Constant dialogues. */ + private static final List dialogues = new ArrayList(); + + /** The Constant stairs. */ + private static final List stairs = new ArrayList(); + + /** The most players online. */ + private static int mostPlayersOnline = 0; + + /** The start time. */ + public static long startTime; + + /** + * The main method. + * + * @param args + * the arguments + */ + public static void main(String[] args) { + printTag(); + + System.setOut(new ConsoleProxy(System.out)); + System.setErr(new ConsoleProxy(System.err)); + if (args.length == 1) { + if (args[0].equalsIgnoreCase("-verbose") || args[0].equalsIgnoreCase("-v") || args[0].equalsIgnoreCase("-local") || args[0].equalsIgnoreCase("-l")) { + local = true; + } + } + + // 1. Load up any configuration. + try { + SettingsLoader.load(); + if (SettingsLoader.contains("verbose_mode")) { + local = SettingsLoader.getValueAsBoolean("verbose_mode"); + } + + System.out.println("Bootstrapping " + Settings.SERVER_NAME + " emulator..."); + + LandscapeKeys.loadKeys(LANDSCAPE_KEYS_FILE); + GroundManager.create(); + ItemDef.load(); + SqlManager.load(); + MagicManager.load(); + NpcCombatDef.load(); + + // XXX: Run the various configuration parsers. + ParserLookup.doRun(); + + RubyInterpreter.loadScripts(new File(SCRIPTS_DIRECTORY)); + } catch (Exception ex) { + ex.printStackTrace(); + return; + } + + // 2. Start the server engine. + ServerEngine.start(); + + // Start the networking system. + ServerBootstrap bootstrap = new ServerBootstrap(); + bootstrap.setPipelineFactory(new OsirisPipelineFactory()); + ExecutorService e = Executors.newCachedThreadPool(); + ChannelFactory factory = new NioServerSocketChannelFactory(e, e); + bootstrap.setFactory(factory); + bootstrap.bind(new InetSocketAddress(PORT)); + startTime = System.currentTimeMillis(); + System.out.println("Bootstrap complete!"); + } + + /** + * Prints the tag. + */ + private static void printTag() { + System.out.println(); + System.out.println(" ,-----. ,--. ,--. "); + System.out.println(" ' .-. ' ,---. `--',--.--.`--' ,---. "); + System.out.println(" | | | |( .-' ,--.| .--',--.( .-' "); + System.out.println(" ' '-' '.-' `)| || | | |.-' `)"); + System.out.println(" `-----' `----' `--'`--' `--'`----'"); + System.out.println(); + } + + /** + * Gets the players. + * + * @return the players + */ + public static CharacterGroup getPlayers() { + return players; + } + + /** + * Gets the npcs. + * + * @return the npcs + */ + public static CharacterGroup getNpcs() { + return npcs; + } + + /** + * Gets the quick chat messages. + * + * @return the quick chat messages + */ + public static Hashtable getQuickChatMessages() { + return quickChatMessages; + } + + /** + * Find player. + * + * @param uid + * the uid + * @return the character + */ + public static Player findPlayer(int uid) { + for (Player player : getPlayers()) + if (player.getUniqueId() == uid) + return player; + return null; + } + + /** + * Find player. + * + * @param name + * the name + * @return the player + */ + public static Player findPlayer(String name) { + for (Player player : getPlayers()) + if (player.getUsername().equalsIgnoreCase(name)) + return player; + return null; + } + + /** + * Sets the most players online. + * + * @param most + * the new most players online + */ + public static void setMostPlayersOnline(int most) { + mostPlayersOnline = most; + } + + /** + * Gets the most players online. + * + * @return the most players online + */ + public static int getMostPlayersOnline() { + return mostPlayersOnline; + } + + /** + * Checks if is local. + * + * @return true, if is local + */ + public static boolean isLocal() { + return local; + } + + /** + * Gets the dialogues. + * + * @return the dialogues + */ + public static List getDialogues() { + return dialogues; + } + + /** + * Gets the stairs. + * + * @return the stairs + */ + public static List getStairs() { + return stairs; + } + +} diff --git a/src/osiris/ServerEngine.java b/src/osiris/ServerEngine.java new file mode 100644 index 0000000..f296995 --- /dev/null +++ b/src/osiris/ServerEngine.java @@ -0,0 +1,332 @@ +package osiris; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import static osiris.util.Settings.NPC_MOVEMENT_TIME; +import static osiris.util.Settings.TICKRATE; + +import java.util.Comparator; +import java.util.LinkedList; +import java.util.PriorityQueue; +import java.util.Queue; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; + +import osiris.game.event.GameEvent; +import osiris.game.event.impl.ServiceRequestEvent; +import osiris.game.model.Character; +import osiris.game.model.DeathManager; +import osiris.game.model.InfoUpdater; +import osiris.game.model.Npc; +import osiris.game.model.NpcMovement; +import osiris.game.model.Player; +import osiris.game.model.combat.CombatManager; +import osiris.game.model.combat.Hit; +import osiris.game.model.ground.GroundManager; +import osiris.util.Settings; + +// TODO: Auto-generated Javadoc +/** + * The main server engine. + * + * @author Blake + * + */ +public class ServerEngine implements Runnable, ThreadFactory { + + /** + * The scheduler. + */ + private static ScheduledExecutorService scheduler; + + /** + * The Constant eventsToProcess. + */ + private static final Queue eventsToProcess = new LinkedList(); + + /** The hit queue. */ + private static PriorityQueue hitQueue; + + /** The death manager. */ + private static DeathManager deathManager; + + /** The lockdown. */ + private static boolean lockdown = false; + + /** + * The ticks. + */ + private static int ticks = 0; + + /** + * Starts the server engine. + */ + public static void start() { + // Create a new ServerEngine instance. + ServerEngine instance = new ServerEngine(); + deathManager = new DeathManager(); + + // Initialize the scheduler and schedule the engine instance. + scheduler = Executors.newSingleThreadScheduledExecutor(instance); + scheduler.scheduleAtFixedRate(instance, 0, TICKRATE, TimeUnit.MILLISECONDS); + + // Schedule any other utilities. + scheduler.scheduleAtFixedRate(GroundManager.getManager(), 0, 3, TimeUnit.SECONDS); + scheduler.scheduleAtFixedRate(new NpcMovement(), 0, NPC_MOVEMENT_TIME, TimeUnit.SECONDS); + scheduler.scheduleAtFixedRate(new InfoUpdater(), 0, 600, TimeUnit.MILLISECONDS); + scheduler.scheduleAtFixedRate(deathManager, 0, 600, TimeUnit.MILLISECONDS); + + hitQueue = new PriorityQueue(1, new Comparator() { + @Override + public int compare(Hit a, Hit b) { + return b.getTimer().getStart() - a.getTimer().getStart(); + } + }) { + private static final long serialVersionUID = 412420407031048943L; + + @Override + public boolean add(Hit hit) { + return hit.getAttacker().isInSight(hit.getVictim()) && super.add(hit); + } + }; + + } + + /** + * Stops the server engine. + */ + public static void stop() { + getScheduler().shutdown(); + } + + /** + * Queues a game event for processing. + * + * @param event + * the event + */ + public static void queueGameEvent(GameEvent event) { + synchronized (eventsToProcess) { + eventsToProcess.add(event); + } + } + + /* + * (non-Javadoc) + * + * @see java.lang.Runnable#cycle() + */ + + @Override + public void run() { + + // 1. Process all game events. + synchronized (eventsToProcess) { + while (!eventsToProcess.isEmpty()) { + // Get the next event. + GameEvent event = eventsToProcess.poll(); + try { + // Attempt to process the event. + event.process(); + event.getPlayer().getTimeoutTimer().reset(); + } catch (Exception ex) { + // Whoops! Something went wrong. Clean up. + if (event.getPlayer() != null) { + ex.printStackTrace(); + // Log out the problematic player. + event.getPlayer().logout(); + } else if (event.getClass() == ServiceRequestEvent.class || event.getClass().getSuperclass() == ServiceRequestEvent.class) { + // Or close the problematic service requesting channel. + ServiceRequestEvent e = (ServiceRequestEvent) event; + e.getChannel().close(); + } else { + // Or what the fuck happened? + ex.printStackTrace(); + } + } + event = null; + } + } + + // 2. Update all players. + try { + synchronized (Main.getPlayers()) { + CombatManager.updateHits(); + // Perform the pre-update procedures for players and NPCs. + for (Player player : Main.getPlayers()) { + if ((player.getInteractingCharacter() instanceof Player && !Main.getPlayers().contains((Player) player.getInteractingCharacter())) || player.getInteractingCharacter() instanceof Npc && !Main.getNpcs().contains((Npc) player.getInteractingCharacter())) + player.setInteractingCharacter(null); + + // Timeout checking. + if (player.getTimeoutTimer().elapsed() > Settings.TIMEOUT_DELAY) { + player.logout(); + continue; + } + + // Pre-updating. + player.handleQueuedUpdateBlocks(); + player.getCombatManager().cycle(); + player.getMovementQueue().cycle(); + } + for (Npc npc : Main.getNpcs()) { + if ((npc.getInteractingCharacter() instanceof Player && !Main.getPlayers().contains((Player) npc.getInteractingCharacter())) || npc.getInteractingCharacter() instanceof Npc && !Main.getNpcs().contains((Npc) npc.getInteractingCharacter())) + npc.setInteractingCharacter(null); + npc.handleQueuedUpdateBlocks(); + npc.getCombatManager().cycle(); + npc.getMovementQueue().cycle(); + } + + // Perform the actual update procedures. + for (Player player : Main.getPlayers()) { + player.getPlayerUpdater().run(); + player.getNpcUpdater().run(); + } + + // Perform the post-update procedures for players and NPCs. + for (Player player : Main.getPlayers()) { + player.getUpdateFlags().reset(); + player.getUpdateBlocks().clear(); + player.adjustSpecialEnergy(player.specialRegainRate()); + } + for (Npc npc : Main.getNpcs()) { + npc.getUpdateFlags().reset(); + npc.getUpdateBlocks().clear(); + } + } + } catch (Exception ex) { + // Something bad happened while updating. + ex.printStackTrace(); + } + + // 3. Update any other server logic. + try { + + for (Player player : Main.getPlayers()) { + if (ticks % 6 == 0) { + if (!player.getMovementQueue().getRunningQueue() && player.getEnergy() < 100) { + player.setEnergy(player.getEnergy() + 1, true); + } + } + } + } catch (Exception ex) { + // Something bad happened while processing logic. + ex.printStackTrace(); + } + + ticks++; // Record this tick. + } + + /** + * Gets the ticks. + * + * @return the ticks + */ + public static int getTicks() { + return ticks; + } + + /* + * (non-Javadoc) + * + * @see java.util.concurrent.ThreadFactory#newThread(java.lang.Runnable) + */ + + @Override + public Thread newThread(Runnable task) { + // Make sure our thread gets the highest priority. + Thread thread = new Thread(task); + thread.setPriority(Thread.MAX_PRIORITY); + thread.setName("ServerEngine"); + return thread; + } + + /** + * Send global message. + * + * @param msg + * the msg + */ + public static void sendGlobalMessage(String msg) { + for (Player player : Main.getPlayers()) { + player.getEventWriter().sendMessage(msg); + } + } + + /** + * Gets the scheduler. + * + * @return the scheduler + */ + public static ScheduledExecutorService getScheduler() { + return scheduler; + } + + /** + * Gets the hit queue. + * + * @return the hit queue + */ + public static PriorityQueue getHitQueue() { + return hitQueue; + } + + /** + * Gets the death manager. + * + * @return the death manager + */ + public static DeathManager getDeathManager() { + return deathManager; + } + + /** + * Sets the lockdown. + */ + public static void setLockdown() { + lockdown = true; + } + + /** + * Checks if is under lockdown. + * + * @return true, if is under lockdown + */ + public static boolean isUnderLockdown() { + return lockdown; + } + + /** + * Static graphic. + * + * @param character + * the character + * @param id + * the id + * @param height + * the height + */ + public static void staticGraphic(Character character, int id, int height) { + for (Player player : Main.getPlayers()) + if (player.isInSight(character)) + player.getEventWriter().spawnGfx(id, height, character.getPosition()); + } + +} diff --git a/src/osiris/data/NpcDropLoader.java b/src/osiris/data/NpcDropLoader.java new file mode 100644 index 0000000..d55bcae --- /dev/null +++ b/src/osiris/data/NpcDropLoader.java @@ -0,0 +1,72 @@ +package osiris.data; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +import osiris.game.model.NpcDropContainer; + +// TODO: Auto-generated Javadoc +/** + * The Class NpcDropLoader. + * + * @author Boomer + */ +public class NpcDropLoader { + + /** The drop containers. */ + private static List dropContainers = new LinkedList(); + + /** + * Adds the. + * + * @param container + * the container + */ + public static void add(NpcDropContainer container) { + dropContainers.add(container); + } + + /** + * Gets the containers. + * + * @param npcId + * the npc id + * @return the containers + */ + public static ArrayList getContainers(int npcId) { + ArrayList containers = new ArrayList(); + for (NpcDropContainer container : dropContainers) + for (int i : container.getNpcs()) + if (i == npcId) + containers.add(container); + return containers; + } + + /** + * Gets the all containers. + * + * @return the all containers + */ + public static List getAllContainers() { + return dropContainers; + } +} diff --git a/src/osiris/data/PlayerData.java b/src/osiris/data/PlayerData.java new file mode 100644 index 0000000..1de4648 --- /dev/null +++ b/src/osiris/data/PlayerData.java @@ -0,0 +1,411 @@ +package osiris.data; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Arrays; + +import osiris.Main; +import osiris.ServerEngine; +import osiris.data.sql.SqlManager; +import osiris.game.model.Player; +import osiris.game.model.Position; +import osiris.game.model.Skills; +import osiris.game.model.item.Item; +import osiris.util.Utilities; + +// TODO: Auto-generated Javadoc +/** + * The Class PlayerData. + * + * @author Boomer + */ +public class PlayerData { + + /** + * Saves the player. + * + * @param player + * The player's instance. + * @return true on success, false on failure. + */ + public static boolean save(Player player) { + int uniqueId = player.getUniqueId(); + + // Skills + try { + saveSkills(SqlManager.manager.playerSkillsReplaceStatement, player, uniqueId); + } catch (SQLException e) { + e.printStackTrace(); + return false; + } + + // Contacts + try { + saveContacts(SqlManager.manager.playerContactsReplaceStatement, player, uniqueId); + } catch (SQLException e) { + e.printStackTrace(); + return false; + } + + // Items + try { + saveItems(SqlManager.manager.playerItemsReplaceStatement, player, uniqueId); + } catch (SQLException e) { + e.printStackTrace(); + return false; + } + + // Settings + try { + saveSettings(SqlManager.manager.playerSettingsReplaceStatement, player, uniqueId); + } catch (SQLException e) { + e.printStackTrace(); + return false; + } + return true; + } + + /* + * Return Codes: 1 = wait 2 seconds 2 = success 3 = wrong pass 4 = disabled + * 5 = logged in already 6 = reload client 7 = full 8 = unable to connect 9 + * = too many connections from ip 10= bad session id 11 = please try again + * 12 = members 13 = could not complete login 14 = server is being updated + * 16 = login attemps exceeded 17 = standing in members area 20 = invalid + * login request 21 = just left another world + */ + + /** + * Loads the player. + * + * @param playerName + * The player's name. + * @param pass + * The player's password. + * @param player + * The player's instance. + * @return A return code to be sent to the client. + */ + public static int load(String playerName, String pass, Player player) { + // Is the server down for an update? + if (ServerEngine.isUnderLockdown()) { + return 14; + } + + // Is the player already logged in? + for (Player p : Main.getPlayers()) { + if (p.getUsername().equalsIgnoreCase(playerName)) + return 5; + } + + if (SqlManager.manager != null) { + try { + ResultSet playerResults = SqlManager.manager.getResults(SqlManager.manager.serverPlayerResultsStatement, playerName); + + boolean foundUser = false; + while (playerResults.next()) { + player.setUniqueId(playerResults.getInt("id")); + + if (player.getUniqueId() == -1) + return 3; + + int salt = playerResults.getInt("salt"); + // Is the player's password correct? + if (!playerResults.getString("password").equals(Utilities.smfHash(pass, salt))) + return 3; + + // Retrieve the player's status. + player.setPlayerStatus(playerResults.getInt("status")); + foundUser = true; + } + + // Player doesn't exist. + if (!foundUser || player.getUniqueId() == -1) + return 3; + + // Retrieve items for the player. + ResultSet itemsResults = SqlManager.manager.getResults(SqlManager.manager.playerItemsResultsStatement, player.getUniqueId()); + if (itemsResults != null) { + while (itemsResults.next()) { + String inventoryIds = itemsResults.getString("inventory_item_ids").replaceAll("\\[", "").replaceAll("\\]", ""); + String inventoryAmounts = itemsResults.getString("inventory_item_amounts").replaceAll("\\[", "").replaceAll("\\]", ""); + + String[] ids = inventoryIds.split(", "); + String[] amounts = inventoryAmounts.split(", "); + Item[] items = new Item[ids.length]; + + for (int i = 0; i < ids.length; i++) { + int id = Integer.parseInt(ids[i]); + + if (id == -1) { + items[i] = null; + } else { + items[i] = Item.create(id, Integer.parseInt(amounts[i])); + } + } + + player.getInventory().setItems(items); + + String bankIds = itemsResults.getString("bank_item_ids").replaceAll("\\[", "").replaceAll("\\]", ""); + String bankAmounts = itemsResults.getString("bank_item_amounts").replaceAll("\\[", "").replaceAll("\\]", ""); + + ids = bankIds.split(", "); + amounts = bankAmounts.split(", "); + items = new Item[ids.length]; + + for (int i = 0; i < ids.length; i++) { + int id = Integer.parseInt(ids[i]); + + if (id == -1) { + items[i] = null; + } else { + items[i] = Item.create(id, Integer.parseInt(amounts[i])); + } + } + + player.getBank().getMain().setItems(items); + + String equipmentIds = itemsResults.getString("equipment_item_ids").replaceAll("\\[", "").replaceAll("\\]", ""); + String equipmentAmounts = itemsResults.getString("equipment_item_amounts").replaceAll("\\[", "").replaceAll("\\]", ""); + + ids = equipmentIds.split(", "); + amounts = equipmentAmounts.split(", "); + items = new Item[ids.length]; + + for (int i = 0; i < ids.length; i++) { + int id = Integer.parseInt(ids[i]); + + if (id == -1) { + items[i] = null; + } else { + items[i] = Item.create(id, Integer.parseInt(amounts[i])); + } + } + + player.getEquipment().setItems(items); + } + } + + // Retrieve the contacts for the player. + ResultSet contactsResults = SqlManager.manager.getResults(SqlManager.manager.playerContactsResultsStatement, player.getUniqueId()); + if (contactsResults != null) { + while (contactsResults.next()) { + String friendIds = contactsResults.getString("friends").replaceAll("\\[", "").replaceAll("\\]", ""); + String[] friends = friendIds.split(", "); + + for (String friend : friends) { + if (friend.length() != 0) { + player.getContacts().getFriends().add(Long.parseLong(friend)); + } + } + + String ignoreIds = contactsResults.getString("ignores").replaceAll("\\[", "").replaceAll("\\]", ""); + String[] ignores = ignoreIds.split(", "); + + for (String ignore : ignores) { + if (ignore.length() != 0) { + player.getContacts().getIgnores().add(Long.parseLong(ignore)); + } + } + } + } + + // Retrieve the settings for the player. + ResultSet settingsResults = SqlManager.manager.getResults(SqlManager.manager.playerSettingsResultsStatement, player.getUniqueId()); + if (settingsResults != null) { + while (settingsResults.next()) { + player.setLoadedPosition(new Position(settingsResults.getInt("position_x"), settingsResults.getInt("position_y"), settingsResults.getInt("position_z"))); + player.setEnergy(settingsResults.getInt("run_energy"), false); + player.setSpecialEnergy(settingsResults.getInt("special_energy"), false); + player.setAttackStyle(settingsResults.getInt("attack_style"), false); + String spellBook = settingsResults.getString("spell_book"); + player.setSpellBook(spellBook); + player.getAppearance().setGender(settingsResults.getInt("gender")); + player.getSettings().setAutoRetaliate(settingsResults.getInt("auto_retaliate") == 1); + String[] emoteStatuses = settingsResults.getString("emote_status").replaceAll("\\[", "").replaceAll("\\]", "").split(", "); + for (int i = 0; i < emoteStatuses.length; i++) + player.getEmoteStatus()[i] = Integer.parseInt(emoteStatuses[i]); + + } + } + + // Retrieve the skills for the player. + ResultSet skillsResults = SqlManager.manager.getResults(SqlManager.manager.playerSkillsResultsStatement, player.getUniqueId()); + if (skillsResults != null) { + while (skillsResults.next()) { + int columnIndex = 2; + + for (int i = 0; i < Skills.NUMBER_OF_SKILLS; i++) { + int curLevel = skillsResults.getInt(columnIndex++); + columnIndex++; // Omitting maxLevel + double xp = skillsResults.getDouble(columnIndex++); + + player.getSkills().setSkill(i, curLevel, xp, false); + } + } + } + return 2; + } + + catch (Exception e) { + e.printStackTrace(); + return 3; + } + + } + if (player.getUniqueId() == -1) + return 3; + + return 13; + } + + /** + * Saves the player's skills. + * + * @param statement + * The prepared statement for saving. + * @param player + * The player's instance. + * @param uniqueId + * The player's ID. + * @throws SQLException + * On a MySQL failure. + */ + public static void saveSkills(PreparedStatement statement, Player player, int uniqueId) throws SQLException { + int[] curLevels = player.getSkills().getCurLevels(); + int[] maxLevels = player.getSkills().getMaxLevels(); + double[] experience = player.getSkills().getExperience(); + + int totalCurLevels = 0; + int totalMaxLevels = 0; + double totalExp = 0.0; + + int statementIndex = 0; + + // Iterate through the skills. + for (int i = 0; i < Skills.NUMBER_OF_SKILLS; i++) { + statement.setInt(++statementIndex, curLevels[i]); + statement.setInt(++statementIndex, maxLevels[i]); + statement.setDouble(++statementIndex, experience[i]); + + totalCurLevels += curLevels[i]; + totalMaxLevels += maxLevels[i]; + totalExp += experience[i]; + } + + statement.setInt(++statementIndex, totalCurLevels); + statement.setInt(++statementIndex, totalMaxLevels); + statement.setDouble(++statementIndex, totalExp); + + statement.setInt(++statementIndex, uniqueId); + statement.executeUpdate(); + } + + /** + * Saves the player's contacts. + * + * @param statement + * The prepared statement for saving. + * @param player + * The player's instance. + * @param uniqueId + * The player's ID. + * @throws SQLException + * On a MySQL failure. + */ + public static void saveContacts(PreparedStatement statement, Player player, int uniqueId) throws SQLException { + statement.setString(1, Arrays.toString(player.getContacts().getFriends().toArray(new Long[player.getContacts().getFriends().size()]))); + statement.setString(2, Arrays.toString(player.getContacts().getIgnores().toArray(new Long[player.getContacts().getIgnores().size()]))); + statement.setInt(3, uniqueId); + statement.executeUpdate(); + } + + /** + * Saves the player's items. + * + * @param statement + * The prepared statement for saving. + * @param player + * The player's instance. + * @param uniqueId + * The player's ID. + * @throws SQLException + * On a MySQL failure. + */ + public static void saveItems(PreparedStatement statement, Player player, int uniqueId) throws SQLException { + Item[][] containers = new Item[][] { player.getInventory().getItems(), player.getBank().getMain().getItems(), player.getEquipment().getItems() }; + int saveSlot = 1; + + for (Item[] container : containers) { + int[] itemIds = new int[container.length]; + int[] itemNs = new int[container.length]; + + for (int i = 0; i < container.length; i++) { + Item item = container[i]; + + if (item == null) { + itemIds[i] = -1; + itemNs[i] = 0; + } else { + itemIds[i] = item.getId(); + itemNs[i] = item.getAmount(); + } + } + + statement.setString(saveSlot, Arrays.toString(itemIds)); + saveSlot++; + + statement.setString(saveSlot, Arrays.toString(itemNs)); + saveSlot++; + } + + statement.setInt(saveSlot, uniqueId); + statement.executeUpdate(); + } + + /** + * Saves the player's settings. + * + * @param statement + * The prepared statement for saving. + * @param player + * The player's instance. + * @param uniqueId + * The player's ID. + * @throws SQLException + * On a MySQL failure. + */ + public static void saveSettings(PreparedStatement statement, Player player, int uniqueId) throws SQLException { + statement.setInt(1, player.getPosition().getX()); + statement.setInt(2, player.getPosition().getY()); + statement.setInt(3, player.getPosition().getZ()); + statement.setInt(4, player.getEnergy()); + statement.setInt(5, player.getSpecialEnergy()); + statement.setInt(6, player.getAttackStyle()); + statement.setString(7, player.getSpellBook().name()); + statement.setInt(8, player.getAppearance().getGender()); + statement.setInt(9, player.getSettings().isAutoRetaliate() ? 1 : 0); + statement.setString(10, Arrays.toString(player.getEmoteStatus())); + statement.setInt(11, uniqueId); + statement.executeUpdate(); + } + +} diff --git a/src/osiris/data/SettingsLoader.java b/src/osiris/data/SettingsLoader.java new file mode 100644 index 0000000..632f9e6 --- /dev/null +++ b/src/osiris/data/SettingsLoader.java @@ -0,0 +1,186 @@ +package osiris.data; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Date; +import java.util.Hashtable; +import java.util.List; + +// TODO: Auto-generated Javadoc +/** + * The Class SettingsLoader. + * + * @author samuraiblood2 + * + */ +public class SettingsLoader { + + /** The Constant FILE_EXTENSION. */ + private static final String FILE_EXTENSION = ".ini"; + + /** The Constant SETTINGS_LOCATION. */ + private static final String SETTINGS_LOCATION = "./"; + + /** The settings. */ + private static Hashtable settings = new Hashtable(); + + /** + * Searches for all files ending with FILE_EXTENSION and parses them + * accordingly. + * + * @throws IOException + * Signals that an I/O exception has occurred. + */ + public static void load() throws IOException { + boolean found = false; + File root = new File(SETTINGS_LOCATION); + for (File child : root.listFiles()) { + if (child == null) { + continue; + } + + if (!child.getName().endsWith(FILE_EXTENSION)) { + continue; + } + + BufferedReader in = new BufferedReader(new FileReader(child)); + List lines = new ArrayList(); + while (true) { + String line = in.readLine(); + if (line == null) { + break; + } + + lines.add(line); + } + doParsing(lines.toArray(new String[0])); + found = true; + } + + if (!found) { + createDefault(); + load(); + } + } + + /** + * Creates the default. + * + * @throws IOException + * Signals that an I/O exception has occurred. + */ + private static void createDefault() throws IOException { + BufferedWriter out = new BufferedWriter(new FileWriter(new File(SETTINGS_LOCATION + "settings" + FILE_EXTENSION))); + final String[] keys = { "server_port", "server_name", "exp_rate", "sqlite_saving", "server_host", "server_database", "server_username", "server_password", "start_x", "start_y", "start_z" }; + + final String[] values = { "43594", "Osiris", "10", "true", "localhost", "database", "root", "admin", "3082", "3419", "0" }; + + out.write("# Generated on " + new Date()); + out.newLine(); + out.newLine(); + for (int i = 0; i < keys.length; i++) { + out.write(keys[i].toUpperCase() + ": " + values[i]); + out.newLine(); + } + out.flush(); + out.close(); + } + + /** + * Appends the keys and values to a Hashtable for later use. + * + * @param lines + * the lines + */ + private static void doParsing(String[] lines) { + for (int i = 0; i < lines.length; i++) { + String line = lines[i]; + int number = (i + 1); + + if (line == null) { + continue; + } + + if (line.matches("\\s*") || line.startsWith("#")) { + continue; + } + + String[] args = line.split("\\s*(=|:)\\s*"); + if (args.length < 2) { + System.out.println("Missing argument at line " + number); + continue; + } + + String key = args[0]; + String value = args[1]; + settings.put(key.toLowerCase(), value); + } + } + + /** + * Gets the value. + * + * @param key + * the key + * @return the value + */ + public static String getValue(String key) { + return settings.get(key.toLowerCase()); + } + + /** + * Gets the value as int. + * + * @param key + * the key + * @return the value as int + */ + public static int getValueAsInt(String key) { + return Integer.parseInt(getValue(key).trim()); + } + + /** + * Gets the value as boolean. + * + * @param key + * the key + * @return the value as boolean + */ + public static boolean getValueAsBoolean(String key) { + return Boolean.parseBoolean(settings.get(key.toLowerCase())); + } + + /** + * Contains. + * + * @param key + * the key + * @return true, if successful + */ + public static boolean contains(String key) { + return settings.containsKey(key.toLowerCase()); + } +} diff --git a/src/osiris/data/parser/Parser.java b/src/osiris/data/parser/Parser.java new file mode 100644 index 0000000..409590d --- /dev/null +++ b/src/osiris/data/parser/Parser.java @@ -0,0 +1,154 @@ +package osiris.data.parser; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.io.File; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; + +// TODO: Auto-generated Javadoc +/** + * The Class Parser. + * + * @author samuraiblood2 + * + */ +public abstract class Parser implements Runnable { + + /** The file. */ + private File file; + + /** + * Instantiates a new parser with a pre-given directory. + * + * @param file + * the file + */ + public Parser(String file) { + this(new File("./data/config/" + file)); + } + + /** + * Instantiates a new parser. + * + * @param file + * the file + */ + public Parser(File file) { + this.file = file; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Runnable#run() + */ + public void run() { + try { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = factory.newDocumentBuilder(); + Document doc = builder.parse(getFile()); + doc.getDocumentElement().normalize(); + + // XXX: We can use this to parse other files with the same parser. + doIncludeTag(doc); + + onParse(doc); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * Anything done on parse. + * + * @param doc + * the doc + * @throws Exception + * the exception + */ + public abstract void onParse(Document doc) throws Exception; + + /** + * If there is an include tag, we change the file name and run the thread. + * This way we can have XML files calling other files with the same parser. + * + * @param doc + * the doc + */ + private void doIncludeTag(Document doc) { + Element includeElement = (Element) doc.getElementsByTagName("include").item(0); + if (includeElement != null) { + String dir = "./data/config/"; + if (includeElement.getAttribute("dir") != null) { + dir = includeElement.getAttribute("dir"); + } + setFile(new File(dir + includeElement.getAttribute("name"))); + run(); + } + } + + /** + * Gets the value inside the specified tag as a String. + * + * @param element + * the element + * @param tag + * the tag + * @return the text value + */ + public String getTextValue(Element element, String tag) { + NodeList list = element.getElementsByTagName(tag); + + if (list == null) { + return null; + } + + if (list.getLength() <= 0) { + return null; + } + + Element tmp = (Element) list.item(0); + return tmp.getFirstChild().getNodeValue(); + } + + /** + * Sets the file. + * + * @param file + * the file + */ + private void setFile(File file) { + this.file = file; + } + + /** + * Gets the file. + * + * @return the file + */ + public File getFile() { + return file; + } +} diff --git a/src/osiris/data/parser/ParserLookup.java b/src/osiris/data/parser/ParserLookup.java new file mode 100644 index 0000000..009572f --- /dev/null +++ b/src/osiris/data/parser/ParserLookup.java @@ -0,0 +1,76 @@ +package osiris.data.parser; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.util.ArrayList; +import java.util.List; + +import osiris.Main; +import osiris.data.parser.impl.DialogueParser; +import osiris.data.parser.impl.GroundItemParser; +import osiris.data.parser.impl.NpcDropParser; +import osiris.data.parser.impl.NpcSpawnParser; +import osiris.data.parser.impl.PositionChangeParser; +import osiris.data.parser.impl.WorldObjectParser; +import osiris.util.Settings; + +// TODO: Auto-generated Javadoc +/** + * Runs all the various parsers at once. Basically for convenience only. + * + * @author samuraiblood2 + * + */ +public class ParserLookup { + + /** The parsers. */ + private static List parsers = new ArrayList(); + + /** + * Adds the parsers. + * + * TODO: Possibly make a parser for this as well. + * + */ + private static void addParsers() { + parsers.add(new DialogueParser(Settings.DIALOGUES)); + parsers.add(new PositionChangeParser(Settings.POSITION_CHANGE)); + parsers.add(new GroundItemParser(Settings.GROUND_ITEMS)); + parsers.add(new NpcDropParser(Settings.NPC_DROPS)); + parsers.add(new NpcSpawnParser(Settings.NPC_SPAWNS)); + parsers.add(new WorldObjectParser(Settings.WORLD_OBJECTS)); + } + + /** + * Do run. + */ + public static void doRun() { + addParsers(); + for (Parser parser : parsers) { + if (parser == null) { + continue; + } + + if (Main.isLocal()) { + System.out.println("[Parser]: Running '" + parser.getFile().getName() + "' parser..."); + } + new Thread(parser).run(); + } + } +} diff --git a/src/osiris/data/parser/impl/DialogueParser.java b/src/osiris/data/parser/impl/DialogueParser.java new file mode 100644 index 0000000..ad9d8cf --- /dev/null +++ b/src/osiris/data/parser/impl/DialogueParser.java @@ -0,0 +1,95 @@ +package osiris.data.parser.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.util.ArrayList; +import java.util.List; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; + +import osiris.Main; +import osiris.data.parser.Parser; +import osiris.game.model.dialogues.Dialogue; +import osiris.game.model.dialogues.Dialogue.Type; + +// TODO: Auto-generated Javadoc +/** + * The Class DialogueParser. + * + * @author samuraiblood2 + * + */ +public class DialogueParser extends Parser { + + /** + * Instantiates a new dialogue parser. + * + * @param file + * the file + */ + public DialogueParser(String file) { + super(file); + } + + /* + * (non-Javadoc) + * + * @see osiris.data.parser.Parser#onParse(org.w3c.dom.Document) + */ + @Override + public void onParse(Document doc) throws Exception { + NodeList dialogueList = (NodeList) doc.getElementsByTagName("dialogue"); + if (dialogueList == null) { + return; + } + + for (int i = 0; i < dialogueList.getLength(); i++) { + Element dialogueElement = (Element) dialogueList.item(i); + + Type type = Type.valueOf(dialogueElement.getAttribute("type").toUpperCase()); + int id = Integer.parseInt(dialogueElement.getAttribute("id")); + Dialogue dialogue = new Dialogue(type, id); + + String[] params = getTextValue(dialogueElement, "npc").split(","); + for (int l = 0; l < params.length; l++) { + Dialogue clone = new Dialogue(dialogue); + clone.setNpc(Integer.parseInt(params[l])); + + Element linesElement = (Element) dialogueElement.getElementsByTagName("lines").item(0); + clone.setTitle(linesElement.getAttribute("title")); + + NodeList lineList = (NodeList) linesElement.getElementsByTagName("line"); + List lines = new ArrayList(); + for (int j = 0; j < lineList.getLength(); j++) { + Element lineElement = (Element) lineList.item(j); + lines.add(lineElement.getFirstChild().getNodeValue()); + } + + if (clone.getType() == Type.OPTION && lines.size() < 2) { + lines.add(""); + } + clone.setLines(lines); + clone.setNext(Integer.parseInt(getTextValue(dialogueElement, "next"))); + Main.getDialogues().add(clone); + } + } + } +} diff --git a/src/osiris/data/parser/impl/GroundItemParser.java b/src/osiris/data/parser/impl/GroundItemParser.java new file mode 100644 index 0000000..c585886 --- /dev/null +++ b/src/osiris/data/parser/impl/GroundItemParser.java @@ -0,0 +1,92 @@ +package osiris.data.parser.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import static osiris.util.Settings.DEFAULT_Z; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; + +import osiris.data.parser.Parser; +import osiris.game.model.Position; +import osiris.game.model.def.ItemDef; +import osiris.game.model.ground.GroundItem; +import osiris.game.model.ground.GroundManager; +import osiris.game.model.item.Item; + +// TODO: Auto-generated Javadoc +/** + * The Class GroundItemParser. + * + * @author samuraiblood2 + * + */ +public class GroundItemParser extends Parser { + + /** + * Instantiates a new ground item parser. + * + * @param file + * the file + */ + public GroundItemParser(String file) { + super(file); + } + + /* + * (non-Javadoc) + * + * @see osiris.data.parser.Parser#onParse(org.w3c.dom.Document) + */ + @Override + public void onParse(Document doc) throws Exception { + NodeList itemsList = (NodeList) doc.getElementsByTagName("ground"); + if (itemsList == null) { + return; + } + + for (int i = 0; i < itemsList.getLength(); i++) { + Element itemElement = (Element) itemsList.item(i); + int id = Integer.parseInt(itemElement.getAttribute("id")); + + int amount = 1; + if (ItemDef.forId(id).isStackable()) { + String amountAttribute = itemElement.getAttribute("amount").trim(); + if (amountAttribute != null) { + amount = Integer.parseInt(amountAttribute); + } + } + + String respawnAttribute = itemElement.getAttribute("respawn"); + boolean respawn = respawnAttribute != null && Boolean.parseBoolean(respawnAttribute); + + int x = Integer.parseInt(getTextValue(itemElement, "X").trim()); + int y = Integer.parseInt(getTextValue(itemElement, "Y").trim()); + + String zNode = getTextValue(itemElement, "Z"); + int z = DEFAULT_Z; + if (zNode != null) { + z = Integer.parseInt(zNode.trim()); + } + GroundItem item = new GroundItem(Item.create(id, amount).setRespawns(respawn), new Position(x, y, z)); + GroundManager.getManager().dropItem(item); + } + } +} diff --git a/src/osiris/data/parser/impl/NpcDropParser.java b/src/osiris/data/parser/impl/NpcDropParser.java new file mode 100644 index 0000000..1c3670c --- /dev/null +++ b/src/osiris/data/parser/impl/NpcDropParser.java @@ -0,0 +1,98 @@ +package osiris.data.parser.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.util.ArrayList; +import java.util.List; +import java.util.StringTokenizer; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; + +import osiris.data.NpcDropLoader; +import osiris.data.parser.Parser; +import osiris.game.model.NpcDrop; +import osiris.game.model.NpcDropContainer; + +// TODO: Auto-generated Javadoc +/** + * The Class NpcDropParser. + * + * @author Blake Beaupain + * @author Boomer + * @author samuraiblood2 + * + */ +public class NpcDropParser extends Parser { + + /** + * Instantiates a new npc drop parser. + * + * @param file + * the file + */ + public NpcDropParser(String file) { + super(file); + } + + /* + * (non-Javadoc) + * + * @see osiris.data.parser.Parser#onParse(org.w3c.dom.Document) + */ + @Override + public void onParse(Document doc) throws Exception { + NodeList dropList = doc.getElementsByTagName("npcDrop"); + for (int i = 0; i < dropList.getLength(); i++) { + Element dropElement = (Element) dropList.item(i); + NpcDropContainer container; + List npcIds = new ArrayList(); + List npcDrops = new ArrayList(); + + // Get the NPC IDs. + Element npcIdsElement = (Element) dropElement.getElementsByTagName("npcIds").item(0); + if (npcIdsElement != null) { + String value = npcIdsElement.getFirstChild().getNodeValue(); + StringTokenizer st = new StringTokenizer(value, ", "); + while (st.hasMoreTokens()) { + Integer id = Integer.parseInt(st.nextToken()); + npcIds.add(id); + } + } + + // Get the items. + NodeList itemsList = (NodeList) dropElement.getElementsByTagName("item"); + for (int n = 0; n < itemsList.getLength(); n++) { + Element itemElement = (Element) itemsList.item(n); + int itemId = Integer.parseInt(itemElement.getAttribute("id")); + String amount = itemElement.getAttribute("amount"); + double chance = Double.parseDouble(itemElement.getAttribute("chance").replaceAll("%", "")) / 100; + StringTokenizer st = new StringTokenizer(amount, "-"); + if (st.countTokens() == 1) + npcDrops.add(new NpcDrop(itemId, Integer.parseInt(st.nextToken()), chance)); + else + npcDrops.add(new NpcDrop(itemId, Integer.parseInt(st.nextToken()), Integer.parseInt(st.nextToken()), chance)); + } + + container = new NpcDropContainer(npcIds, npcDrops); + NpcDropLoader.add(container); + } + } +} diff --git a/src/osiris/data/parser/impl/NpcSpawnParser.java b/src/osiris/data/parser/impl/NpcSpawnParser.java new file mode 100644 index 0000000..645080e --- /dev/null +++ b/src/osiris/data/parser/impl/NpcSpawnParser.java @@ -0,0 +1,117 @@ +package osiris.data.parser.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import static osiris.util.Settings.DEFAULT_X; +import static osiris.util.Settings.DEFAULT_Y; +import static osiris.util.Settings.DEFAULT_Z; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; + +import osiris.Main; +import osiris.data.parser.Parser; +import osiris.game.model.Npc; +import osiris.game.model.Position; + +// TODO: Auto-generated Javadoc + +/** + * The Class NpcSpawnParser. + * + * @author Blake Beaupain + * @author samuraiblood2 + * @author Boomer + * + */ +public class NpcSpawnParser extends Parser { + + /** + * Instantiates a new npc spawn parser. + * + * @param file + * the file + */ + public NpcSpawnParser(String file) { + super(file); + } + + /* + * (non-Javadoc) + * + * @see osiris.data.parser.Parser#onParse(org.w3c.dom.Document) + */ + @Override + public void onParse(Document doc) throws Exception { + NodeList npcList = (NodeList) doc.getElementsByTagName("npc"); + for (int i = 0; i < npcList.getLength(); i++) { + int npcID = 1; + int posX = DEFAULT_X, posY = DEFAULT_Y, posZ = DEFAULT_Z; + int minX = DEFAULT_X, minY = DEFAULT_Y, maxX = DEFAULT_Z, maxY = DEFAULT_Y; + Position defaultFacing = null; + @SuppressWarnings("unused") + int health = 0, maxHit = 1, accuracy = 0, defence = 0, attackSpeed = 3; + // Load the NPC ID. + Element npcElement = (Element) npcList.item(i); + npcID = Integer.parseInt(npcElement.getAttribute("id")); + + // Load the position. + Element posElement = (Element) npcElement.getElementsByTagName("position").item(0); + if (posElement != null) { + posX = Integer.parseInt(posElement.getAttribute("x")); + posY = Integer.parseInt(posElement.getAttribute("y")); + posZ = Integer.parseInt(posElement.getAttribute("z")); + } + + // Load the movement missile. + Element rangeElement = (Element) npcElement.getElementsByTagName("movementRange").item(0); + if (rangeElement != null) { + minX = Integer.parseInt(rangeElement.getAttribute("minX")); + minY = Integer.parseInt(rangeElement.getAttribute("minY")); + maxX = Integer.parseInt(rangeElement.getAttribute("maxX")); + maxY = Integer.parseInt(rangeElement.getAttribute("maxY")); + } + + Element faceElement = (Element) npcElement.getElementsByTagName("face").item(0); + if (faceElement != null) { + int faceX = posX; + int faceY = posY; + String facing = faceElement.getTextContent().toLowerCase(); + if (facing.contains("north")) + faceY += 1; + else if (facing.contains("south")) + faceY -= 1; + if (facing.contains("west")) + faceX -= 1; + else if (facing.contains("east")) + faceX += 1; + defaultFacing = new Position(faceX, faceY, posZ); + } + + // Make the NPC object and register it with the world. + Npc npc = new Npc(npcID); + npc.getPosition().set(posX, posY, posZ); + npc.setDefaultPosition(npc.getPosition()); + npc.setWalkingRange(minX, minY, maxX, maxY); + npc.setDefaultFacing(defaultFacing); + Main.getNpcs().add(npc); + } + } +} diff --git a/src/osiris/data/parser/impl/PositionChangeParser.java b/src/osiris/data/parser/impl/PositionChangeParser.java new file mode 100644 index 0000000..ca06e46 --- /dev/null +++ b/src/osiris/data/parser/impl/PositionChangeParser.java @@ -0,0 +1,252 @@ +package osiris.data.parser.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; + +import osiris.Main; +import osiris.data.parser.Parser; +import osiris.game.model.Position; + +// TODO: Auto-generated Javadoc +/** + * I The Class PositionChangeParser. + * + * @author samuraiblood2 + * + */ +public class PositionChangeParser extends Parser { + + /** + * Instantiates a new position change parser. + * + * @param file + * the file + */ + public PositionChangeParser(String file) { + super(file); + } + + /* + * (non-Javadoc) + * + * @see osiris.data.parser.Parser#onParse(org.w3c.dom.Document) + */ + @Override + public void onParse(Document doc) throws Exception { + NodeList positionList = (NodeList) doc.getElementsByTagName("pos"); + if (positionList == null) { + return; + } + + for (int i = 0; i < positionList.getLength(); i++) { + Element positionElement = (Element) positionList.item(i); + int id = Integer.parseInt(positionElement.getAttribute("id")); + if (id < 0) { + throw new Exception("Object ID must be higher than 0."); + } + + int x = Integer.parseInt(positionElement.getAttribute("x")); + int y = Integer.parseInt(positionElement.getAttribute("y")); + int z = 0; + if (!positionElement.getAttribute("z").matches("\\s*")) { + z = Integer.parseInt(positionElement.getAttribute("z")); + } + PositionChangeInfo positionInfo = new PositionChangeInfo(id, new Position(x, y, z)); + + Element door = (Element) positionElement.getElementsByTagName("swing").item(0); + if (door != null) { + String currentFace = door.getAttribute("current").toUpperCase(); + WorldObjectParser.Face current = WorldObjectParser.Face.valueOf(currentFace); + if (current == null) { + throw new Exception("Incorrect face value."); + } + positionInfo.setCurrent(current); + positionInfo.setFrom(current); + + String toFace = door.getAttribute("to").toUpperCase(); + WorldObjectParser.Face to = WorldObjectParser.Face.valueOf(toFace); + if (to == null) { + throw new Exception("Incorrect face value."); + } + positionInfo.setTo(to); + positionInfo.setType(Type.DOOR); + } else { + for (int i2 = 0; i2 < 2; i2++) { + Element direction = (Element) positionElement.getElementsByTagName(i2 == 0 ? "up" : "down").item(0); + if (direction == null) { + continue; + } + + Position pos = createPositionFromElement(direction); + if (i2 == 0) { + positionInfo.setMove(0, pos); + } else { + positionInfo.setMove(1, pos); + } + } + + if (positionInfo.getMove(0) == null && positionInfo.getMove(1) == null) { + throw new Exception("Must have the up or down tag present."); + } + positionInfo.setType(Type.STAIR); + } + + Main.getStairs().add(positionInfo); + } + } + + /** + * Creates the position from element. + * + * @param element + * the element + * @return the position + */ + private Position createPositionFromElement(Element element) { + int moveX = Integer.parseInt(element.getAttribute("x")); + int moveY = Integer.parseInt(element.getAttribute("y")); + int moveZ = 0; + if (!element.getAttribute("z").matches("\\s*")) { + moveZ = Integer.parseInt(element.getAttribute("z")); + } + + return new Position(moveX, moveY, moveZ); + } + + public enum Type { + DOOR, STAIR + } + + /** + * The Class PositionChangeInfo. + * + * @author samuraiblood2 + * + */ + public class PositionChangeInfo { + + /** The id. */ + private int id; + + /** The location. */ + private Position location; + + /** The move. */ + private Position[] move; + + /** The face. */ + private WorldObjectParser.Face current, from, to; + + private Type type; + + /** + * Instantiates a new position change info. + * + * @param id + * the id + * @param location + * the location + */ + public PositionChangeInfo(int id, Position location) { + this.id = id; + this.location = location; + move = new Position[2]; + } + + public PositionChangeInfo setType(Type type) { + this.type = type; + return this; + } + + /** + * Gets the id. + * + * @return the id + */ + public int getId() { + return id; + } + + public Type getType() { + return type; + } + + /** + * Sets the lcation. + */ + public void setLocation(Position location) { + this.location = location; + } + + /** + * Gets the location. + * + * @return the location + */ + public Position getLocation() { + return location; + } + + public void setMove(int index, Position position) { + move[index] = position; + } + + public Position getMove(int index) { + return move[index]; + } + + /** + * Sets the face. + * + * @param current + * the new face + */ + public void setCurrent(WorldObjectParser.Face current) { + this.current = current; + } + + /** + * Gets the face. + * + * @return the face + */ + public WorldObjectParser.Face getCurrent() { + return current; + } + + public void setTo(WorldObjectParser.Face to) { + this.to = to; + } + + public WorldObjectParser.Face getTo() { + return to; + } + + public void setFrom(WorldObjectParser.Face from) { + this.from = from; + } + + public WorldObjectParser.Face getFrom() { + return from; + } + } +} \ No newline at end of file diff --git a/src/osiris/data/parser/impl/WorldObjectParser.java b/src/osiris/data/parser/impl/WorldObjectParser.java new file mode 100644 index 0000000..1b3f29c --- /dev/null +++ b/src/osiris/data/parser/impl/WorldObjectParser.java @@ -0,0 +1,172 @@ +package osiris.data.parser.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.util.ArrayList; +import java.util.List; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; + +import osiris.data.parser.Parser; +import osiris.game.model.Position; +import osiris.game.model.WorldObject; +import osiris.game.model.WorldObjects; + +// TODO: Auto-generated Javadoc +/** + * The Class WorldObjectParser. + * + * @author samuraiblood2 + * + */ +public class WorldObjectParser extends Parser { + + /** + * Instantiates a new world object parser. + * + * @param file + * the file + */ + public WorldObjectParser(String file) { + super(file); + } + + /** + * The Enum Face. + * + * @author samuraiblood2 + * + */ + public enum Face { + + NORTH(2), SOUTH(0), EAST(3), WEST(1); + + /** The id. */ + private int id; + + /** + * Instantiates a new face. + * + * @param id + * the id + */ + Face(int id) { + this.id = id; + } + + /** + * Gets the id. + * + * @return the id + */ + public int getId() { + return id; + } + } + + /** + * The Enum Type. + * + * @author samuraiblood2 + * + */ + public enum Type { + + /** The WALL. */ + WALL(0), /** The WAL l_ decor. */ + WALL_DECOR(4), /** The DIA g_ wall. */ + DIAG_WALL(9), /** The WORLD. */ + WORLD(10), /** The ROOF. */ + ROOF(12), /** The FLOOR. */ + FLOOR(22); + + /** The id. */ + private int id; + + /** + * Instantiates a new type. + * + * @param id + * the id + */ + Type(int id) { + this.id = id; + } + + /** + * Gets the id. + * + * @return the id + */ + public int getId() { + return id; + } + } + + /* + * (non-Javadoc) + * + * @see osiris.data.parser.Parser#onParse(org.w3c.dom.Document) + */ + @Override + public void onParse(Document doc) throws Exception { + NodeList objectList = (NodeList) doc.getElementsByTagName("object"); + if (objectList == null) { + return; + } + + List list = new ArrayList(); + for (int i = 0; i < objectList.getLength(); i++) { + Element objectElement = (Element) objectList.item(i); + String idAttribute = objectElement.getAttribute("id"); + int id = 0; + if (idAttribute.equalsIgnoreCase("remove")) { + id = 6951; + } else { + id = Integer.parseInt(idAttribute); + } + + int x = Integer.parseInt(objectElement.getAttribute("x")); + int y = Integer.parseInt(objectElement.getAttribute("y")); + int z = 0; + if (!objectElement.getAttribute("z").contains("")) { + z = Integer.parseInt(objectElement.getAttribute("z")); + } + Face face = Face.WEST; + if (id != 6951) { + face = Face.valueOf(getTextValue(objectElement, "face").toUpperCase().replaceAll(" ", "_").trim()); + } + + String value = getTextValue(objectElement, "type"); + Type type = Type.WORLD; + if (value.equalsIgnoreCase("all")) { + for (int l = 0; l < 22; l++) { + list.add(new WorldObject(id, face.getId(), l, new Position(x, y, z))); + } + break; + } else { + type = Type.valueOf(value.toUpperCase().replaceAll(" ", "_").trim()); + } + list.add(new WorldObject(id, face.getId(), type.getId(), new Position(x, y, z))); + } + WorldObjects.getObjects().addAll(list); + } +} diff --git a/src/osiris/data/sql/SqlConnection.java b/src/osiris/data/sql/SqlConnection.java new file mode 100644 index 0000000..c1efb08 --- /dev/null +++ b/src/osiris/data/sql/SqlConnection.java @@ -0,0 +1,139 @@ +package osiris.data.sql; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; + +import osiris.Main; +import osiris.util.Settings; + +// TODO: Auto-generated Javadoc +/** + * The Class SqlConnection. + * + * @author Boomer + */ +public class SqlConnection { + + /** The password. */ + private final String host, database, username, password; + + /** The connection. */ + private Connection connection; + + /** + * Instantiates a new sql connection. + * + * @param host + * the host + * @param database + * the database + * @param username + * the username + * @param password + * the password + */ + public SqlConnection(final String host, final String database, final String username, final String password) { + this.host = host; + this.database = database; + this.username = username; + this.password = password; + } + + /** + * Gets the connection. + * + * @return the connection + */ + public Connection getConnection() { + try { + if (connection == null || connection.isClosed()) + connect(); + } catch (SQLException e) { + connect(); + } + return connection; + } + + /** + * Connect. + */ + public void connect() { + String tag = "[SQL" + (Settings.SQLITE_SAVING ? "ite" : "") + "]"; + try { + if (Main.isLocal()) { + System.out.println(tag + " Connecting to Database (" + database + ")... "); + } + + if (!Settings.SQLITE_SAVING) { + Class.forName("com.mysql.jdbc.Driver").newInstance(); + String url = "jdbc:mysql://" + host + "/" + database; + connection = DriverManager.getConnection(url, username, password); + } else { + Class.forName("org.sqlite.JDBC"); + connection = DriverManager.getConnection("jdbc:sqlite:./data/database/osirisdb.sqlite"); + + } + + if (Main.isLocal()) { + System.out.println(tag + " SUCCESS!"); + } + } catch (Exception e) { + System.err.println(tag + " FAILURE: " + e); + } + } + + /** + * Gets the host. + * + * @return the host + */ + public String getHost() { + return host; + } + + /** + * Gets the database. + * + * @return the database + */ + public String getDatabase() { + return database; + } + + /** + * Gets the username. + * + * @return the username + */ + public String getUsername() { + return username; + } + + /** + * Gets the password. + * + * @return the password + */ + public String getPassword() { + return password; + } +} diff --git a/src/osiris/data/sql/SqlManager.java b/src/osiris/data/sql/SqlManager.java new file mode 100644 index 0000000..9559c79 --- /dev/null +++ b/src/osiris/data/sql/SqlManager.java @@ -0,0 +1,161 @@ +package osiris.data.sql; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +import osiris.util.Settings; + +// TODO: Auto-generated Javadoc +/** + * The Class SqlManager. + * + * @author Boomer + */ +public class SqlManager { + + /** The server connection. */ + public SqlConnection serverConnection; + + /** The player settings replace statement. */ + public PreparedStatement serverPlayerResultsStatement, serverPlayerReplaceStatement, playerSkillsResultsStatement, playerSkillsReplaceStatement, playerContactsResultsStatement, playerContactsReplaceStatement, playerItemsResultsStatement, playerItemsReplaceStatement, playerSettingsResultsStatement, playerSettingsReplaceStatement; + + /** + * Instantiates a new sql manager. + */ + public SqlManager() { + serverConnection = new SqlConnection(Settings.SERVER_HOST, Settings.SERVER_DATABASE, Settings.SERVER_USERNAME, Settings.SERVER_PASSWORD); + serverConnection.connect(); + + String serverPlayerResultsQuery = "SELECT * FROM players WHERE name = ?"; + String playerSkillsResultsQuery = "SELECT * FROM skills WHERE player_id = ?"; + String playerContactsResultsQuery = "SELECT * FROM contacts WHERE player_id = ?"; + String playerItemsResultsQuery = "SELECT * FROM items WHERE player_id = ?"; + String playerSettingsResultsQuery = "SELECT * FROM settings WHERE player_id = ?"; + + String serverPlayerReplaceQuery = "REPLACE INTO players (`name` ,`password`, `ip`, `status`, `salt`) VALUES (?, ?, ?, ?, ?);"; + String playerSkillsReplaceQuery = "REPLACE INTO skills (`attack_curlevel`, `attack_maxlevel`, `attack_xp`, `defence_curlevel`, `defence_maxlevel`, `defence_xp`, `strength_curlevel`, `strength_maxlevel`, `strength_xp`, `hitpoints_curlevel`, `hitpoints_maxlevel`, `hitpoints_xp`, `range_curlevel`, `range_maxlevel`, `range_xp`, `prayer_curlevel`, `prayer_maxlevel`, `prayer_xp`, `magic_curlevel`, `magic_maxlevel`, `magic_xp`, `cooking_curlevel`, `cooking_maxlevel`, `cooking_xp`, `woodcutting_curlevel`, `woodcutting_maxlevel`, `woodcutting_xp`, `fletching_curlevel`, `fletching_maxlevel`, `fletching_xp`, `fishing_curlevel`, `fishing_maxlevel`, `fishing_xp`, `firemaking_curlevel`, `firemaking_maxlevel`, `firemaking_xp`, `crafting_curlevel`, `crafting_maxlevel`, `crafting_xp`, `smithing_curlevel`, `smithing_maxlevel`, `smithing_xp`, `mining_curlevel`, `mining_maxlevel`, `mining_xp`, `herblore_curlevel`, `herblore_maxlevel`, `herblore_xp`, `agility_curlevel`, `agility_maxlevel`, `agility_xp`, `thieving_curlevel`, `thieving_maxlevel`, `thieving_xp`, `slayer_curlevel`, `slayer_maxlevel`, `slayer_xp`, `farming_curlevel`, `farming_maxlevel`, `farming_xp`, `runecrafting_curlevel`, `runecrafting_maxlevel`, `runecrafting_xp`, `construction_curlevel`, `construction_maxlevel`, `construction_xp`, `hunter_curlevel`, `hunter_maxlevel`, `hunter_xp`, `summoning_curlevel`, `summoning_maxlevel`, `summoning_xp`, `overall_curlevel`, `overall_maxlevel`, `overall_xp`, `player_id`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + String playerContactsReplaceQuery = "REPLACE INTO contacts (`friends`, `ignores`, `player_id`) VALUES (?, ?, ?);"; + String playerItemsReplaceQuery = "REPLACE INTO items (`inventory_item_ids`, `inventory_item_amounts`, `bank_item_ids`, `bank_item_amounts`, `equipment_item_ids`, `equipment_item_amounts`, `player_id`) VALUES (?, ?, ?, ?, ?, ?, ?);"; + String playerSettingsReplaceQuery = "REPLACE INTO settings (`position_x`, `position_y`, `position_z`, `run_energy`, `special_energy`, `attack_style`, `spell_book`, `gender`, `auto_retaliate`, `emote_status`, `player_id`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"; + + try { + serverPlayerResultsStatement = serverConnection.getConnection().prepareStatement(serverPlayerResultsQuery); + serverPlayerReplaceStatement = serverConnection.getConnection().prepareStatement(serverPlayerReplaceQuery); + + playerSkillsReplaceStatement = serverConnection.getConnection().prepareStatement(playerSkillsReplaceQuery); + playerSkillsResultsStatement = serverConnection.getConnection().prepareStatement(playerSkillsResultsQuery); + + playerContactsReplaceStatement = serverConnection.getConnection().prepareStatement(playerContactsReplaceQuery); + playerContactsResultsStatement = serverConnection.getConnection().prepareStatement(playerContactsResultsQuery); + + playerItemsReplaceStatement = serverConnection.getConnection().prepareStatement(playerItemsReplaceQuery); + playerItemsResultsStatement = serverConnection.getConnection().prepareStatement(playerItemsResultsQuery); + + playerSettingsReplaceStatement = serverConnection.getConnection().prepareStatement(playerSettingsReplaceQuery); + playerSettingsResultsStatement = serverConnection.getConnection().prepareStatement(playerSettingsResultsQuery); + + } catch (SQLException e) { + e.printStackTrace(); + } + + } + + /** + * Load. + */ + public static void load() { + manager = new SqlManager(); + } + + /** + * Gets the results. + * + * @param statement + * the statement + * @param value + * the value + * @return the results + */ + public ResultSet getResults(PreparedStatement statement, String value) { + try { + statement.setString(1, value); + return statement.executeQuery(); + } catch (SQLException e) { + e.printStackTrace(); + } + return null; + } + + /** + * Gets the all results. + * + * @param table + * the table + * @return the all results + */ + public ResultSet getAllResults(String table) { + try { + return serverConnection.getConnection().createStatement().executeQuery("SELECT * FROM " + table + ""); + } catch (SQLException e) { + e.printStackTrace(); + } + return null; + } + + /** + * Gets the results. + * + * @param query + * the query + * @return the results + */ + public ResultSet getResults(String query) { + try { + return serverConnection.getConnection().createStatement().executeQuery(query); + } catch (SQLException e) { + e.printStackTrace(); + } + return null; + } + + /** + * Gets the results. + * + * @param statement + * the statement + * @param value + * the value + * @return the results + */ + public ResultSet getResults(PreparedStatement statement, int value) { + try { + statement.setInt(1, value); + return statement.executeQuery(); + } catch (SQLException e) { + e.printStackTrace(); + } + return null; + } + + /** The manager. */ + public static SqlManager manager; +} diff --git a/src/osiris/game/action/Action.java b/src/osiris/game/action/Action.java new file mode 100644 index 0000000..cc4fb2b --- /dev/null +++ b/src/osiris/game/action/Action.java @@ -0,0 +1,123 @@ +package osiris.game.action; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.action.impl.TeleportAction; +import osiris.game.model.Character; +import osiris.game.model.Death; +import osiris.game.model.Player; + +// TODO: Auto-generated Javadoc + +/** + * A timed game action. + * + * @author Blake + */ +public abstract class Action implements Runnable { + + /** + * The player. + */ + private final Character character; + + /** + * Instantiates a new action. + * + * @param character + * the character + */ + public Action(Character character) { + this.character = character; + if (!canRun()) + return; + Action current = character.getCurrentAction(); + if (current != null) { + current.cancel(); + } + character.setCurrentAction(this); + } + + /** + * Cancel. + */ + public abstract void onCancel(); + + // public abstract boolean inProgress(); + + /** + * Cancel. + */ + public void cancel() { + onCancel(); + character.setCurrentAction(null); + } + + /** + * Gets the player. + * + * @return the player + */ + public Character getCharacter() { + return character; + } + + /** + * Gets the player. + * + * @return the player + */ + public Player getPlayer() { + return (Player) character; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Runnable#run() + */ + public void run() { + if (!canRun()) + return; + else + onRun(); + } + + /** + * Can run. + * + * @return true, if successful + */ + public boolean canRun() { + if (character == null) + return false; + Action current = character.getCurrentAction(); + if (current != null) { + if (current instanceof Death || current instanceof TeleportAction) + if (!this.equals(current)) + return false; + } + return true; + } + + /** + * On run. + */ + public abstract void onRun(); +} diff --git a/src/osiris/game/action/DelayAction.java b/src/osiris/game/action/DelayAction.java new file mode 100644 index 0000000..568eb0f --- /dev/null +++ b/src/osiris/game/action/DelayAction.java @@ -0,0 +1,78 @@ +package osiris.game.action; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.model.Character; + +// TODO: Auto-generated Javadoc +/** + * The Class DelayAction. + * + * @author Boomer + */ +public class DelayAction extends Action { + + /** The delay. */ + private long delay; + + /** + * Instantiates a new action. + * + * @param character + * the character + * @param delay + * the delay + */ + public DelayAction(Character character, long delay) { + super(character); + if (!character.getActionTimer().completed()) + cancel(); + this.delay = delay; + } + + /* + * (non-Javadoc) + * + * @see osiris.game.action.Action#run() + */ + @Override + public void run() { + super.run(); + getCharacter().getActionTimer().setDelay(delay, null); + + } + + /* + * (non-Javadoc) + * + * @see osiris.game.action.Action#onCancel() + */ + @Override + public void onCancel() { + } + + /* + * (non-Javadoc) + * + * @see osiris.game.action.Action#onRun() + */ + @Override + public void onRun() { + } +} diff --git a/src/osiris/game/action/DistancedAction.java b/src/osiris/game/action/DistancedAction.java new file mode 100644 index 0000000..589e978 --- /dev/null +++ b/src/osiris/game/action/DistancedAction.java @@ -0,0 +1,119 @@ +package osiris.game.action; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.model.Character; +import osiris.game.model.Position; +import osiris.util.DistancedTask; + +// TODO: Auto-generated Javadoc + +/** + * The Class DistancedAction. + * + * @author Blake + * + */ +public abstract class DistancedAction extends Action { + + /** The task. */ + private DistancedTask task; + + /** + * Instantiates a new distanced action. + * + * @param character + * the character + * @param position + * the position + * @param distance + * the distance + */ + public DistancedAction(Character character, Position position, int distance) { + super(character); + + // Initialize the task. + task = new DistancedTask(getCharacter(), position, distance) { + @Override + public void execute() { + DistancedAction.this.execute(); + } + }; + } + + /** + * Instantiates a new distanced action. + * + * @param character + * the character + * @param other + * the other + * @param distance + * the distance + */ + public DistancedAction(Character character, final Character other, final int distance) { + super(character); + + // Initialize the task. + task = new DistancedTask(getCharacter(), other.getPosition(), distance) { + @Override + public void execute() { + DistancedAction.this.execute(); + } + + public Position getPosition() { + return other.getPosition(); + } + }; + } + + /** + * Execute. + */ + public abstract void execute(); + + /* + * (non-Javadoc) + * + * @see java.lang.Runnable#cycle() + */ + @Override + public final void onRun() { + task.run(); + } + + /* + * (non-Javadoc) + * + * @see osiris.game.action.Action#cancelAttack() + */ + @Override + public void onCancel() { + task.cancel(); + } + + /** + * Gets the task. + * + * @return the task + */ + public DistancedTask getTask() { + return task; + } +} diff --git a/src/osiris/game/action/ItemActionListener.java b/src/osiris/game/action/ItemActionListener.java new file mode 100644 index 0000000..98331a5 --- /dev/null +++ b/src/osiris/game/action/ItemActionListener.java @@ -0,0 +1,44 @@ +package osiris.game.action; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.action.item.ItemAction; + +// TODO: Auto-generated Javadoc +/** + * The listener interface for receiving itemAction events. The class that is + * interested in processing a itemAction event implements this interface, and + * the object created with that class is registered with a component using the + * component's addItemActionListener method. When + * the itemAction event occurs, that object's appropriate + * method is invoked. + * + * @author Blakeman8192 + */ +public interface ItemActionListener { + + /** + * On item action. + * + * @param action + * the action + */ + public void onItemAction(ItemAction action); + +} diff --git a/src/osiris/game/action/ItemActionListeners.java b/src/osiris/game/action/ItemActionListeners.java new file mode 100644 index 0000000..8729fa8 --- /dev/null +++ b/src/osiris/game/action/ItemActionListeners.java @@ -0,0 +1,114 @@ +package osiris.game.action; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import osiris.game.action.item.ItemAction; +import osiris.game.action.item.ItemOnItemAction; +import osiris.game.action.item.impl.BuryBonesListener; +import osiris.game.action.item.impl.FiremakingListener; +import osiris.game.model.item.Item; + +// TODO: Auto-generated Javadoc +/** + * The Class ItemActionListeners. + */ +public class ItemActionListeners { + + /** The listener map. */ + public static Map> listenerMap; + + static { + listenerMap = new HashMap>(); + // Register listeners here. + addListener(new BuryBonesListener(), 526, 528, 530, 532, 534, 536); + addListener(new FiremakingListener(), 590); + } + + /** + * Fire item action. + * + * @param action + * the action + */ + public static void fireItemAction(ItemAction action) { + List listeners = new LinkedList(); + List primary = listenerMap.get(action.getItem().getId()); + if (primary != null) + listeners.addAll(primary); + if (action instanceof ItemOnItemAction) { + Item usedOn = ((ItemOnItemAction) action).getUsedOn(); + if (usedOn != null) { + List other = listenerMap.get(usedOn.getId()); + if (other != null) + listeners.addAll(other); + } + } + if (listeners.size() == 0) { + System.out.println("Couldn't find listener for " + action.getItem().getId()); + return; + } + for (ItemActionListener listener : listeners) { + listener.onItemAction(action); + } + } + + /** + * Adds the listener. + * + * @param listener + * the listener + * @param itemIDs + * the item i ds + */ + public static void addListener(ItemActionListener listener, int... itemIDs) { + for (int itemID : itemIDs) { + List listeners = listenerMap.get(itemID); + if (listeners == null) { + listeners = new LinkedList(); + listenerMap.put(itemID, listeners); + } + listeners.add(listener); + } + } + + /** + * Adds the listeners. + * + * @param itemID + * the item id + * @param listeners + * the listeners + */ + public static void addListeners(int itemID, ItemActionListener... listeners) { + List list = listenerMap.get(itemID); + if (list == null) { + list = new LinkedList(); + listenerMap.put(itemID, list); + } + for (ItemActionListener listener : listeners) { + list.add(listener); + } + } + +} diff --git a/src/osiris/game/action/ObjectActionListener.java b/src/osiris/game/action/ObjectActionListener.java new file mode 100644 index 0000000..1ee152a --- /dev/null +++ b/src/osiris/game/action/ObjectActionListener.java @@ -0,0 +1,44 @@ +package osiris.game.action; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.action.object.ObjectAction; + +// TODO: Auto-generated Javadoc +/** + * The listener interface for receiving objectAction events. The class that is + * interested in processing a objectAction event implements this interface, and + * the object created with that class is registered with a component using the + * component's addObjectActionListener method. When + * the objectAction event occurs, that object's appropriate + * method is invoked. + * + * @author Blake Beaupain + */ +public interface ObjectActionListener { + + /** + * On object action. + * + * @param objectAction + * the object action + */ + public void onObjectAction(ObjectAction objectAction); + +} diff --git a/src/osiris/game/action/ObjectActionListeners.java b/src/osiris/game/action/ObjectActionListeners.java new file mode 100644 index 0000000..3b72b74 --- /dev/null +++ b/src/osiris/game/action/ObjectActionListeners.java @@ -0,0 +1,139 @@ +package osiris.game.action; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import static osiris.game.model.skills.Mining.ADAMANTITE; +import static osiris.game.model.skills.Mining.CLAY; +import static osiris.game.model.skills.Mining.COAL; +import static osiris.game.model.skills.Mining.COPPER; +import static osiris.game.model.skills.Mining.GOLD; +import static osiris.game.model.skills.Mining.IRON; +import static osiris.game.model.skills.Mining.MITHRIL; +import static osiris.game.model.skills.Mining.RUNITE; +import static osiris.game.model.skills.Mining.SILVER; +import static osiris.game.model.skills.Mining.TIN; +import static osiris.game.model.skills.Woodcutting.*; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import osiris.Main; +import osiris.data.parser.impl.PositionChangeParser; +import osiris.game.action.object.ObjectAction; +import osiris.game.action.object.impl.CookingListener; +import osiris.game.action.object.impl.PositionChangeActionListener; +import osiris.game.action.object.impl.RockActionListener; +import osiris.game.action.object.impl.TreeActionListener; + +// TODO: Auto-generated Javadoc +/** + * The Class ObjectActionListeners. + * + * @author Blake Beaupain + */ +public class ObjectActionListeners { + + /** The listener map. */ + public static Map> listenerMap; + + /** + * Fire object action. + * + * @param action + * the action + */ + public static void fireObjectAction(ObjectAction action) { + List listeners = listenerMap.get(action.getObjectID()); + if (listeners == null) { + System.out.println("Couldn't find listerer for " + action.getObjectID()); + return; + } + for (ObjectActionListener listener : listeners) { + listener.onObjectAction(action); + } + } + + /** + * Adds the listener. + * + * @param listener + * the listener + * @param objectIDs + * the object i ds + */ + public static void addListener(ObjectActionListener listener, int... objectIDs) { + for (int objectID : objectIDs) { + List listeners = listenerMap.get(objectID); + if (listeners == null) { + listeners = new LinkedList(); + listenerMap.put(objectID, listeners); + } + listeners.add(listener); + } + } + + /** + * Adds the listeners. + * + * @param objectID + * the object id + * @param listeners + * the listeners + */ + public static void addListeners(int objectID, ObjectActionListener... listeners) { + List list = listenerMap.get(objectID); + if (list == null) { + list = new LinkedList(); + listenerMap.put(objectID, list); + } + for (ObjectActionListener listener : listeners) { + list.add(listener); + } + } + + static { + listenerMap = new HashMap>(); + + // Woodcutting. + TreeActionListener treeActionListener = new TreeActionListener(); + addListener(treeActionListener, TREE_1, TREE_2, TREE_3, TREE_4, TREE_OAK, TREE_WILLOW_1, TREE_WILLOW_2, TREE_WILLOW_3, TREE_WILLOW_4, TREE_MAPLE, TREE_YEW, TREE_MAGIC); + + // Mining + RockActionListener rockActionListener = new RockActionListener(); + addListener(rockActionListener, COPPER); + addListener(rockActionListener, TIN); + addListener(rockActionListener, CLAY); + addListener(rockActionListener, IRON); + addListener(rockActionListener, COAL); + addListener(rockActionListener, SILVER); + addListener(rockActionListener, GOLD); + addListener(rockActionListener, MITHRIL); + addListener(rockActionListener, ADAMANTITE); + addListener(rockActionListener, RUNITE); + + addListener(new CookingListener(true), 2732); + for (PositionChangeParser.PositionChangeInfo info : Main.getStairs()) { + ObjectActionListeners.addListener(new PositionChangeActionListener(info), info.getId()); + } + + } + +} diff --git a/src/osiris/game/action/TickedAction.java b/src/osiris/game/action/TickedAction.java new file mode 100644 index 0000000..abf32e2 --- /dev/null +++ b/src/osiris/game/action/TickedAction.java @@ -0,0 +1,183 @@ +package osiris.game.action; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import static osiris.util.Settings.TICKRATE; + +import java.util.Random; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import osiris.ServerEngine; +import osiris.game.model.Character; +import osiris.util.StopWatch; + +// TODO: Auto-generated Javadoc +/** + * A ticked action. + * + * @author Blake + * + */ +public abstract class TickedAction extends Action { + + /** + * The ticks. + */ + private int ticks; + + /** + * The starting value for random tick values. + */ + private int inclusive; + + /** + * The ending value for random tick values. + */ + private int exclusive; + + /** + * The task. + */ + private ScheduledFuture task; + + /** + * Generates a random tick based off of the inclusive and exclusive values. + * + * @param character + * The character. + * @param inclusive + * The starting value. + * @param exclusive + * The ending value. + */ + public TickedAction(Character character, int inclusive, int exclusive) { + super(character); + this.inclusive = inclusive; + this.exclusive = exclusive; + + Random rand = new Random(); + this.ticks = (rand.nextInt(exclusive - inclusive) + inclusive); + } + + /** + * Instantiates a new ticked action. + * + * @param character + * the character + * @param ticks + * the ticks + */ + public TickedAction(Character character, int ticks) { + super(character); + this.ticks = ticks; + } + + /** + * Execute. + */ + public abstract void execute(); + + /* + * (non-Javadoc) + * + * @see osiris.game.action.Action#onRun() + */ + @Override + public final void onRun() { + final StopWatch timer = new StopWatch(); + task = ServerEngine.getScheduler().scheduleWithFixedDelay(new Runnable() { + @Override + public void run() { + if (timer.elapsed() >= ticks) { + TickedAction.this.execute(); + timer.reset(); + } + } + }, 0, TICKRATE, TimeUnit.MILLISECONDS); + } + + /* + * (non-Javadoc) + * + * @see osiris.game.action.Action#cancelAttack() + */ + @Override + public void onCancel() { + task.cancel(false); + } + + /** + * Sets the current tick to the given value. + * + * @param ticks + * The new tick value. + */ + public void setTicks(int ticks) { + this.ticks = ticks; + } + + /** + * Gets the current tick value. + * + * @return The tick value. + */ + public int getTicks() { + return ticks; + } + + /** + * Sets the inclusive value for a random tick. + * + * @param inclusive + * The starting value. + */ + public void setInclusive(int inclusive) { + this.inclusive = inclusive; + } + + /** + * Gets the inclusive value. + * + * @return The ending value. + */ + public int getInclusive() { + return inclusive; + } + + /** + * Sets the exclusive value for a random tick. + * + * @param exclusive + * The ending value. + */ + public void setExclusive(int exclusive) { + this.exclusive = exclusive; + } + + /** + * Gets the exclusive value. + * + * @return The starting value. + */ + public int getExclusive() { + return exclusive; + } + +} diff --git a/src/osiris/game/action/impl/BankAction.java b/src/osiris/game/action/impl/BankAction.java new file mode 100644 index 0000000..478055d --- /dev/null +++ b/src/osiris/game/action/impl/BankAction.java @@ -0,0 +1,102 @@ +package osiris.game.action.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.action.Action; +import osiris.game.model.Player; +import osiris.game.model.item.Bank; + +// TODO: Auto-generated Javadoc +/** + * The Class BankAction. + * + * @author Boomer + */ +public class BankAction extends Action { + + /** The bank. */ + private Bank bank; + + /** The player. */ + private Player player; + + /** + * Instantiates a new bank action. + * + * @param player + * the player + */ + public BankAction(Player player) { + super(player); + this.bank = player.getBank(); + this.player = player; + run(); + } + + /* + * (non-Javadoc) + * + * @see osiris.game.action.Action#onCancel() + */ + @Override + public void onCancel() { + closeBank(); + } + + /* + * (non-Javadoc) + * + * @see osiris.game.action.Action#onRun() + */ + @Override + public void onRun() { + openBank(); + } + + /** + * Open bank. + */ + public void openBank() { + player.getEventWriter().openSideLockingInterface(762, 763); + player.getEventWriter().sendConfig2(563, 4194304); + player.getEventWriter().sendConfig2(1248, -2013265920); + player.getEventWriter().sendBankOptions(); + bank.setBankOpen(true); + } + + /** + * Close bank. + */ + public void closeBank() { + player.getEventWriter().removeSideLockingInterface(); + bank.setBankOpen(false); + if (player.getXValue() != null) + player.getXValue().cancel(false); + } + + /** + * Gets the bank. + * + * @return the bank + */ + public Bank getBank() { + return bank; + } + +} \ No newline at end of file diff --git a/src/osiris/game/action/impl/CombatAction.java b/src/osiris/game/action/impl/CombatAction.java new file mode 100644 index 0000000..1b5f486 --- /dev/null +++ b/src/osiris/game/action/impl/CombatAction.java @@ -0,0 +1,88 @@ +package osiris.game.action.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.action.Action; +import osiris.game.model.Character; + +// TODO: Auto-generated Javadoc +/** + * The Class CombatAction. + * + * @author Boomer + */ +public class CombatAction extends Action { + + /** The victim. */ + private final Character victim; + + /** The spell slot. */ + private int spellSlot; + + /** + * Instantiates a new distanced action. + * + * @param character + * the character + * @param victim + * the victim + */ + public CombatAction(Character character, Character victim) { + super(character); + this.victim = victim; + this.spellSlot = -1; + } + + /** + * Instantiates a new combat action. + * + * @param character + * the character + * @param victim + * the victim + * @param spellSlot + * the spell slot + */ + public CombatAction(Character character, Character victim, int spellSlot) { + super(character); + this.victim = victim; + this.spellSlot = spellSlot; + character.getMovementQueue().reset(); + } + + /* + * (non-Javadoc) + * + * @see osiris.game.action.Action#onCancel() + */ + @Override + public void onCancel() { + getCharacter().getCombatManager().resetAttackVars(); + } + + /* + * (non-Javadoc) + * + * @see osiris.game.action.Action#onRun() + */ + @Override + public void onRun() { + getCharacter().getCombatManager().attackCharacter(victim, spellSlot); + } +} diff --git a/src/osiris/game/action/impl/DeathItemsScreenAction.java b/src/osiris/game/action/impl/DeathItemsScreenAction.java new file mode 100644 index 0000000..7965da4 --- /dev/null +++ b/src/osiris/game/action/impl/DeathItemsScreenAction.java @@ -0,0 +1,75 @@ +package osiris.game.action.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.util.ArrayList; +import java.util.Arrays; + +import osiris.game.action.Action; +import osiris.game.model.item.Item; + +// TODO: Auto-generated Javadoc +/** + * The Class DeathItemsScreenAction. + * + * @author Boomer + */ +public class DeathItemsScreenAction extends Action { + + /** + * Instantiates a new action. + * + * @param character + * the character + */ + public DeathItemsScreenAction(osiris.game.model.Character character) { + super(character); + } + + /* + * (non-Javadoc) + * + * @see osiris.game.action.Action#onCancel() + */ + @Override + public void onCancel() { + getPlayer().getEventWriter().removeSideLockingInterface(); + } + + /* + * (non-Javadoc) + * + * @see osiris.game.action.Action#onRun() + */ + @Override + public void onRun() { + getPlayer().getEventWriter().openSideLockingInterface(102, 149); + getPlayer().getEventWriter().setAccessMask(211, 0, 2, 6684690, 4); + getPlayer().getEventWriter().setAccessMask(212, 0, 2, 6684693, 42); + + ArrayList items = getPlayer().getItemsKeptOnDeath(); + int[] itemIds = new int[4]; + Arrays.fill(itemIds, -1); + for (int i = 0; i < items.size(); i++) { + itemIds[i] = items.get(i).getId(); + } + Object[] params = { 11510, 12749, "", 0, itemIds[3], itemIds[2], itemIds[1], itemIds[0], items.size(), 0 }; + getPlayer().getEventWriter().runScript(118, params, "iioooiisii"); + } +} diff --git a/src/osiris/game/action/impl/DialogueAction.java b/src/osiris/game/action/impl/DialogueAction.java new file mode 100644 index 0000000..f0728b4 --- /dev/null +++ b/src/osiris/game/action/impl/DialogueAction.java @@ -0,0 +1,103 @@ +package osiris.game.action.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.action.Action; +import osiris.game.model.Character; +import osiris.game.model.dialogues.Dialogue; +import osiris.game.model.dialogues.Dialogue.Type; +import osiris.util.Utilities; + +// TODO: Auto-generated Javadoc +/** + * The Class DialogueAction. + * + * @author samuraiblood2 + * + */ +public class DialogueAction extends Action { + + /** The dialogue. */ + private Dialogue dialogue; + + /** + * Instantiates a new dialogue action. + * + * @param character + * the character + * @param dialogue + * the dialogue + */ + public DialogueAction(Character character, Dialogue dialogue) { + super(character); + this.dialogue = dialogue; + } + + /* + * (non-Javadoc) + * + * @see osiris.game.action.Action#onCancel() + */ + @Override + public void onCancel() { + getPlayer().getEventWriter().sendCloseChatboxInterface(); + } + + /* + * (non-Javadoc) + * + * @see osiris.game.action.Action#onRun() + */ + @Override + public void onRun() { + int inter = 241; + switch (dialogue.getType()) { + case OPTION: + int start = ((dialogue.getLines().length > 2) ? 228 : 227); + inter = start + dialogue.getLines().length; + getPlayer().getEventWriter().sendChatboxInterface(inter); + break; + + case NPC: + inter = 240 + dialogue.getLines().length; + getPlayer().getEventWriter().sendChatboxInterface(inter); + getPlayer().getEventWriter().sendInterfaceAnimation(inter, 2, 9847); + getPlayer().getEventWriter().sendInterfaceNpc(inter, 2, dialogue.getNpc()); + getPlayer().getEventWriter().sendString(dialogue.getTitle(), inter, 3); + break; + + case PLAYER: + break; + } + + int child = dialogue.getType() == Type.OPTION ? 2 : 4; + for (int i = 0; i < dialogue.getLines().length; i++) { + String line = dialogue.getLines()[i]; + if (line == null) { + continue; + } + + if (line.contains("NAME")) { + line = line.replaceAll("NAME", Utilities.toProperCase(getPlayer().getUsername())); + } + getPlayer().getEventWriter().sendString(line, inter, child++); + } + getPlayer().setCurrentlyOpenDialogue(dialogue); + } +} diff --git a/src/osiris/game/action/impl/DisplaySelectOptionAction.java b/src/osiris/game/action/impl/DisplaySelectOptionAction.java new file mode 100644 index 0000000..f6f178b --- /dev/null +++ b/src/osiris/game/action/impl/DisplaySelectOptionAction.java @@ -0,0 +1,98 @@ +package osiris.game.action.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.action.Action; +import osiris.game.model.Character; + +// TODO: Auto-generated Javadoc +/** + * Displays the "What would you like to build?" chat interface. + * + * @author samuraiblood2 + * + */ +public class DisplaySelectOptionAction extends Action { + + /** The interface ID. */ + private int id; + + /** The size of the model's being displayed. */ + private int size; + + /** The various item ID's. */ + private int[] items; + + /** + * Instantiates a new display select option event. + * + * @param character + * the character + * @param id + * The interface ID. + * @param size + * The size of the model's being displayed. + * @param items + * The various item ID's. + */ + public DisplaySelectOptionAction(Character character, int id, int size, int[] items) { + super(character); + this.id = id; + this.size = size; + this.items = items; + } + + /* + * (non-Javadoc) + * + * @see osiris.game.action.Action#onCancel() + */ + @Override + public void onCancel() { + getPlayer().getEventWriter().sendCloseChatboxInterface(); + } + + /* + * (non-Javadoc) + * + * @see osiris.game.action.Action#onRun() + */ + @Override + public void onRun() { + + /** + * The child interface. XXX: This should remain 2 until more then one + * item is being displayed. + */ + int child = 2; + + /** + * Displays the interface. + */ + getPlayer().getEventWriter().sendChatboxInterface(id); + + /** + * Iterates through all the item ID's and displays the models on the + * specified interface. + */ + for (int i = 0; i < items.length; i++) { + getPlayer().getEventWriter().sendInterfaceItem(id, child++, size, items[i]); + } + } +} diff --git a/src/osiris/game/action/impl/DropItemAction.java b/src/osiris/game/action/impl/DropItemAction.java new file mode 100644 index 0000000..e7d349f --- /dev/null +++ b/src/osiris/game/action/impl/DropItemAction.java @@ -0,0 +1,81 @@ +package osiris.game.action.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.action.Action; +import osiris.game.model.Character; +import osiris.game.model.ground.GroundItem; +import osiris.game.model.ground.GroundManager; +import osiris.game.model.item.Item; + +// TODO: Auto-generated Javadoc +/** + * The Class DropItemAction. + * + * @author Boomer + */ +public class DropItemAction extends Action { + + /** The item id. */ + private int slot, itemId; + + /** + * Instantiates a new action. + * + * @param character + * the character + * @param slot + * the slot + * @param itemId + * the item id + */ + public DropItemAction(Character character, int slot, int itemId) { + super(character); + this.slot = slot; + this.itemId = itemId; + } + + /* + * (non-Javadoc) + * + * @see osiris.game.action.Action#onCancel() + */ + @Override + public void onCancel() { + } + + /* + * (non-Javadoc) + * + * @see osiris.game.action.Action#onRun() + */ + @Override + public void onRun() { + if (getPlayer() == null || getPlayer().getInventory() == null || getPlayer().getInventory().getItem(slot) == null) + return; + Item item = getPlayer().getInventory().getItem(slot).clone(); + if (item == null || item.getId() != itemId) + return; + if (!getPlayer().getInventory().removeBySlot(slot, item.getAmount())) + return; + GroundItem ground = new GroundItem(item, getPlayer()); + GroundManager.getManager().dropItem(ground); + + } +} diff --git a/src/osiris/game/action/impl/EmoteAction.java b/src/osiris/game/action/impl/EmoteAction.java new file mode 100644 index 0000000..7c56735 --- /dev/null +++ b/src/osiris/game/action/impl/EmoteAction.java @@ -0,0 +1,95 @@ +package osiris.game.action.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.action.Action; +import osiris.game.model.Character; +import osiris.game.update.block.AnimationBlock; +import osiris.game.update.block.GraphicsBlock; +import osiris.util.StopWatch; + +// TODO: Auto-generated Javadoc +/** + * The Class EmoteAction. + * + * @author Boomer + */ +public class EmoteAction extends Action { + + /** The graphic id. */ + private int emoteId, graphicId = -1; + + /** The emote duration. */ + private StopWatch emoteDuration; + + /** + * Instantiates a new ticked action. + * + * @param character + * the character + * @param emoteId + * the emote id + */ + public EmoteAction(Character character, int emoteId) { + super(character); + this.emoteId = emoteId; + emoteDuration = new StopWatch(); + } + + /** + * Instantiates a new emote action. + * + * @param character + * the character + * @param emoteId + * the emote id + * @param graphicId + * the graphic id + */ + public EmoteAction(Character character, int emoteId, int graphicId) { + this(character, emoteId); + this.graphicId = graphicId; + } + + /* + * (non-Javadoc) + * + * @see osiris.game.action.Action#onRun() + */ + @Override + public void onRun() { + getCharacter().addUpdateBlock(new AnimationBlock(getCharacter(), emoteId, 0)); + if (graphicId != -1) + getCharacter().addUpdateBlock(new GraphicsBlock(getCharacter(), graphicId, 0)); + } + + /* + * (non-Javadoc) + * + * @see osiris.game.action.Action#onCancel() + */ + @Override + public void onCancel() { + if (emoteDuration.elapsed() < 3) { + getCharacter().addUpdateBlock(new AnimationBlock(getCharacter(), -1, 0)); + getCharacter().addUpdateBlock(new GraphicsBlock(getCharacter(), -1, 0)); + } + + } +} diff --git a/src/osiris/game/action/impl/EquipmentScreenAction.java b/src/osiris/game/action/impl/EquipmentScreenAction.java new file mode 100644 index 0000000..dca111c --- /dev/null +++ b/src/osiris/game/action/impl/EquipmentScreenAction.java @@ -0,0 +1,78 @@ +package osiris.game.action.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.action.Action; +import osiris.game.model.Character; +import osiris.game.model.def.ItemDef; +import osiris.game.model.item.Item; +import osiris.util.Settings; + +// TODO: Auto-generated Javadoc +/** + * The Class EquipmentScreenAction. + * + * @author Boomer + */ +public class EquipmentScreenAction extends Action { + + /** + * Instantiates a new action. + * + * @param character + * the character + */ + public EquipmentScreenAction(Character character) { + super(character); + } + + /* + * (non-Javadoc) + * + * @see osiris.game.action.Action#onCancel() + */ + @Override + public void onCancel() { + getPlayer().getEventWriter().removeSideLockingInterface(); + } + + /* + * (non-Javadoc) + * + * @see osiris.game.action.Action#onRun() + */ + @Override + public void onRun() { + getPlayer().getMovementQueue().reset(); + getPlayer().getEventWriter().openSideLockingInterface(667, 149); + int[] bonuses = new int[Settings.BONUSES.length]; + for (Item item : getPlayer().getEquipment().getItems()) { + if (item != null) { + ItemDef def = ItemDef.forId(item.getId()); + for (int i = 0; i < def.getBonuses().length; i++) { + if (i == 12) { + continue; + } + bonuses[i] += def.getBonus(i); + } + } + } + getPlayer().getEventWriter().sendBonus(bonuses); + } +} diff --git a/src/osiris/game/action/impl/FoodAction.java b/src/osiris/game/action/impl/FoodAction.java new file mode 100644 index 0000000..3625495 --- /dev/null +++ b/src/osiris/game/action/impl/FoodAction.java @@ -0,0 +1,144 @@ +package osiris.game.action.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.action.Action; +import osiris.game.model.Character; +import osiris.game.model.def.ItemDef; +import osiris.game.model.effect.Effect; +import osiris.game.model.effect.ExpiringEffect; +import osiris.game.model.effect.FoodEffect; + +/** + * The Class FoodAction. + * + * @author Boomer + */ +public class FoodAction extends Action { + + /** The food. */ + private Food food; + + /** The item id. */ + private int itemId, itemSlot; + + /** + * Instantiates a new food action. + * + * @param character + * the character + * @param food + * the food + * @param itemSlot + * the item slot + * @param itemId + * the item id + */ + public FoodAction(Character character, Food food, int itemSlot, int itemId) { + super(character); + this.food = food; + this.itemId = itemId; + this.itemSlot = itemSlot; + } + + /* + * (non-Javadoc) + * + * @see osiris.game.action.Action#onRun() + */ + @Override + public void onRun() { + for (Effect effect : getCharacter().getEffects()) { + if (effect instanceof FoodEffect) { + return; + } + } + if (food == null) + return; + + ExpiringEffect effect = new FoodEffect(itemSlot, itemId, food.getHeal()); + getCharacter().addEffect(effect); + effect.execute(getCharacter()); + } + + /* + * (non-Javadoc) + * + * @see osiris.game.action.Action#onCancel() + */ + @Override + public void onCancel() { + } + + /** + * Calculate food. + * + * @param itemId + * the item id + * @return the food + */ + public static Food calculateFood(int itemId) { + String value = ItemDef.forId(itemId).getName().toUpperCase().replaceAll(" ", "_"); + Food food = null; + try { + food = Food.valueOf(value); + } catch (IllegalArgumentException e) { + } + return food; + } + + /** + * The Enum Food. + * + * TODO: Add the rest of the foods. TODO: Check if the current foods have + * the correct heal amount. + * + * @author samuraiblood2 + */ + public enum Food { + + // XXX: Meat + COOKED_CHIKEN(3), COOKED_MEAT(3), COOKED_KARAMBWAN(18), + + // XXX: Fish + ANCHOIVES(1), SHRIMPS(3), BREAD(5), HERRING(5), MACKEREL(6), TROUT(7), COD(7), PIKE(8), SALMON(9), TUNA(10), LOBSTER(12), BASS(13), SWORDFISH(14), MONKFISH(16), SHARK(20), TURTLE(21), MANTA_RAY(22); + + /** The heal. */ + private int heal; + + /** + * Instantiates a new food. + * + * @param heal + * the heal + */ + private Food(int heal) { + this.heal = heal; + } + + /** + * Gets the heal. + * + * @return the heal + */ + public int getHeal() { + return heal; + } + } +} diff --git a/src/osiris/game/action/impl/HomeTeleportAction.java b/src/osiris/game/action/impl/HomeTeleportAction.java new file mode 100644 index 0000000..5dcb8b2 --- /dev/null +++ b/src/osiris/game/action/impl/HomeTeleportAction.java @@ -0,0 +1,103 @@ +package osiris.game.action.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.action.TickedAction; +import osiris.game.model.Character; +import osiris.game.model.Position; +import osiris.game.update.block.AnimationBlock; +import osiris.game.update.block.GraphicsBlock; +import osiris.util.StopWatch; + +// TODO: Auto-generated Javadoc +/** + * The Class HomeTeleportAction. + * + * @author Boomer + */ +public class HomeTeleportAction extends TickedAction { + + /** The Constant DELAY. */ + public static final int DELAY = 1; + + /** The Constant graphics. */ + public static final int[] animations = { 1723, 1724, 1725, 2798, 2799, 2800, 4847, 4848, 4849, 4850, 4851 }, graphics = { 800, 801, 802, 1703, 1704, 1705, 1706, 1707, 1708, 1711, 1712 }; + + /** The timer. */ + private StopWatch timer; + + /** The increment. */ + private int increment; + + /** The to. */ + private Position to; + + /** The ignore home timer. */ + private boolean ignoreHomeTimer; + + /** + * Instantiates a new action. + * + * @param character + * the character + * @param to + * teleporting to + */ + public HomeTeleportAction(Character character, Position to) { + super(character, 1); + character.getMovementQueue().reset(); + character.addUpdateBlock(new AnimationBlock(character, 1722, 0)); + this.to = to; + this.increment = 0; + this.timer = new StopWatch(); + } + + /** + * Ignore home timer. + * + * @return the home teleport action + */ + public HomeTeleportAction ignoreHomeTimer() { + this.ignoreHomeTimer = true; + return this; + } + + /* + * (non-Javadoc) + * + * @see osiris.game.action.TickedAction#execute() + */ + @Override + public void execute() { + if (timer.elapsed() >= DELAY) { + if (increment == animations.length) { + this.cancel(); + new TeleportAction(getCharacter(), to, TeleportAction.TeleportType.HOME).run(); + if (!ignoreHomeTimer) + getCharacter().getHomeTeleTimer().reset(); + return; + } + timer.reset(); + getCharacter().addUpdateBlock(new AnimationBlock(getCharacter(), animations[increment], 0)); + getCharacter().addUpdateBlock(new GraphicsBlock(getCharacter(), graphics[increment], 0)); + increment++; + } + } + +} diff --git a/src/osiris/game/action/impl/PickupItemAction.java b/src/osiris/game/action/impl/PickupItemAction.java new file mode 100644 index 0000000..17dc906 --- /dev/null +++ b/src/osiris/game/action/impl/PickupItemAction.java @@ -0,0 +1,90 @@ +package osiris.game.action.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.action.DistancedAction; +import osiris.game.model.Character; +import osiris.game.model.Player; +import osiris.game.model.Position; +import osiris.game.model.def.ItemDef; +import osiris.game.model.ground.GroundItem; +import osiris.game.model.ground.GroundManager; + +/** + * The Class GroundItemAction. + * + * @author samuraiblood2 + * + */ +public class PickupItemAction extends DistancedAction { + + /** The id. */ + private int id; + + /** The position. */ + private Position position; + + /** + * Instantiates a new ground item action. + * + * @param character + * the character + * @param position + * the position + * @param id + * the id + */ + public PickupItemAction(Character character, Position position, int id) { + super(character, position, 1); + this.id = id; + this.position = position; + } + + /* + * (non-Javadoc) + * + * @see osiris.game.action.DistancedAction#execute() + */ + @Override + public void execute() { + if (getCharacter() instanceof Player) { + Player player = (Player) getCharacter(); + if (player == null) { + return; + } + + if (player.getInventory().isFull() && (!ItemDef.forId(id).isStackable() || (ItemDef.forId(id).isStackable() && player.getInventory().getItemById(id) == null))) { + player.getEventWriter().sendMessage("You don't have enough room in your inventory!"); + return; + } + + for (GroundItem item : player.getGroundItems()) { + if (item.getPosition().equals(position) && item.getItem().getId() == id) { + if (!getPlayer().getInventory().add(item.getItem())) + getPlayer().getEventWriter().sendMessage("You have no room open in your inventory!"); + else { + GroundManager.getManager().pickupItem(item); + } + break; + } + } + } + } + +} diff --git a/src/osiris/game/action/impl/SkillAction.java b/src/osiris/game/action/impl/SkillAction.java new file mode 100644 index 0000000..c9c09f0 --- /dev/null +++ b/src/osiris/game/action/impl/SkillAction.java @@ -0,0 +1,242 @@ +package osiris.game.action.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.action.TickedAction; +import osiris.game.model.Player; +import osiris.game.model.Skills; +import osiris.game.model.def.ItemDef; +import osiris.game.model.item.Item; +import osiris.game.model.skills.Skill; +import osiris.game.update.block.AnimationBlock; + +// TODO: Auto-generated Javadoc +/** + * The Class SkillAction. + * + * @author Boomer + */ +public class SkillAction extends TickedAction { + + /** The max attempts. */ + private int skillId, levelRequired, iterationsQueued, iterationsCompleted, emote, attempts, maxAttempts; + + /** The tools required. */ + private Item[] itemsRemoved, itemsGained, toolsRequired; + + /** The experience gained. */ + private double experienceGained; + + /** The success message. */ + private String successMessage; + + /** The skill. */ + private Skill skill; + + /** + * Instantiates a new ticked action. + * + * @param player + * the player + * @param skill + * the skill + * @param skillId + * the skill id + * @param levelRequired + * the level required + * @param expGained + * the exp gained + * @param emote + * the emote + * @param toolsRequired + * the tools required + * @param itemsRemoved + * the items removed + * @param itemsGained + * the items gained + * @param successMessage + * the success message + * @param iterationsQueued + * the iterations queued + */ + public SkillAction(Player player, Skill skill, int skillId, int levelRequired, double expGained, int emote, Item[] toolsRequired, Item[] itemsRemoved, Item[] itemsGained, String successMessage, int iterationsQueued) { + super(player, skill.calculateCooldown()); + this.skill = skill; + this.skillId = skillId; + this.levelRequired = levelRequired; + this.iterationsCompleted = 0; + this.itemsRemoved = itemsRemoved; + this.itemsGained = itemsGained; + this.toolsRequired = toolsRequired; + this.experienceGained = expGained; + this.emote = emote; + this.successMessage = successMessage; + this.iterationsQueued = iterationsQueued; + this.attempts = 0; + this.maxAttempts = -1; + player.getMovementQueue().reset(); + } + + /** + * Sets the max attempts. + * + * @param attempts + * the attempts + * @return the skill action + */ + public SkillAction setMaxAttempts(int attempts) { + this.maxAttempts = attempts; + return this; + } + + /** + * Sets the items gained. + * + * @param itemsGained + * the new items gained + */ + public void setItemsGained(Item[] itemsGained) { + this.itemsGained = itemsGained; + } + + /** + * Sets the success message. + * + * @param message + * the new success message + */ + public void setSuccessMessage(String message) { + this.successMessage = message; + } + + /** + * Sets the experience gained. + * + * @param exp + * the new experience gained + */ + public void setExperienceGained(double exp) { + this.experienceGained = exp; + } + + /** + * Sets the level required. + * + * @param level + * the new level required + */ + public void setLevelRequired(int level) { + this.levelRequired = level; + } + + /* + * (non-Javadoc) + * + * @see osiris.game.action.TickedAction#execute() + */ + @Override + public void execute() { + + if (getPlayer().getSkills().currentLevel(skillId) < levelRequired) { + getPlayer().getEventWriter().sendMessage("You need a level of at least " + levelRequired + " " + Skills.SKILL_NAMES[skillId] + " to do that."); + cancel(); + return; + } + + if (toolsRequired.length > 0) { + for (Item item : toolsRequired) { + if (item == null) { + continue; + } + + if (getPlayer().getInventory().amountOfItem(item.getId()) < item.getAmount()) { + getPlayer().getEventWriter().sendMessage("You need a " + ItemDef.forId(item.getId()).getName() + " to do that."); + cancel(); + return; + } + } + } + + if (emote != -1) + getPlayer().addUpdateBlock(new AnimationBlock(getPlayer(), emote, 0)); + + if (!skill.canIterate()) { + attempts++; + if (maxAttempts != -1 && attempts == maxAttempts) + cancel(); + return; + } + + Item[] invBefore = getPlayer().getInventory().getItems(); + if (itemsRemoved.length > 0) { + for (Item item : itemsRemoved) { + if (item == null) + continue; + + if (!getPlayer().getInventory().removeById(item.getId(), item.getAmount(), false)) { + getPlayer().getEventWriter().sendMessage("You do not have all the resources required!"); + cancel(); + getPlayer().getInventory().setItems(invBefore); + getPlayer().getInventory().refresh(); + return; + } + } + } + + if (itemsGained.length > 0) { + if (!getPlayer().getInventory().canFitAll(itemsGained)) { + getPlayer().getEventWriter().sendMessage("You do not have enough room for that!"); + cancel(); + getPlayer().getInventory().setItems(invBefore); + getPlayer().getInventory().refresh(); + return; + } + + getPlayer().addAllItems(itemsGained); + } else + getPlayer().getInventory().refresh(); + + if (successMessage != null) { + getPlayer().getEventWriter().sendMessage(successMessage); + } + + iterationsCompleted++; + getPlayer().getSkills().addExp(skillId, experienceGained); + if (iterationsQueued != -1 && iterationsCompleted == iterationsQueued) { + skill.onCompletion(); + cancel(); + return; + } else { + setTicks(skill.calculateCooldown()); + } + } + + /* + * (non-Javadoc) + * + * @see osiris.game.action.Action#cancel() + */ + @Override + public void cancel() { + getPlayer().getEventWriter().sendCloseChatboxInterface(); + getPlayer().getEventWriter().sendCloseInterface(); + getPlayer().getEventWriter().removeSideLockingInterface(); + super.cancel(); + } +} diff --git a/src/osiris/game/action/impl/SwitchAutoCastAction.java b/src/osiris/game/action/impl/SwitchAutoCastAction.java new file mode 100644 index 0000000..70f0e01 --- /dev/null +++ b/src/osiris/game/action/impl/SwitchAutoCastAction.java @@ -0,0 +1,132 @@ +package osiris.game.action.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.action.Action; +import osiris.game.model.Player; +import osiris.game.model.item.Item; +import osiris.game.model.magic.MagicManager; +import osiris.game.model.magic.SpellBook; +import osiris.util.Settings; + +// TODO: Auto-generated Javadoc +/** + * The Class SwitchAutoCastAction. + * + * @author Boomer + */ +public class SwitchAutoCastAction extends Action { + + /** The spell id. */ + private int spellId; + + /** The defence. */ + private boolean defence; + + /** The completed. */ + private boolean completed = false; + + /** + * Instantiates a new action. + * + * @param player + * the player + * @param defence + * the defence + */ + public SwitchAutoCastAction(Player player, boolean defence) { + super(player); + this.spellId = -1; + this.defence = defence; + SpellBook spellBook = player.getSpellBook(); + if (spellBook == SpellBook.LUNAR) { + cancel(); + return; + } + Item weapon = player.getEquipment().getItem(Settings.SLOT_WEAPON); + if (weapon == null) { + cancel(); + return; + } + if ((spellBook == SpellBook.ANCIENT && weapon.getId() != 4675) || (spellBook == SpellBook.NORMAL && weapon.getId() == 4675)) { + player.getEventWriter().sendMessage("You can only autocast ANCIENT spells with an ANCIENT staff!"); + cancel(); + return; + } + player.getEventWriter().sendTab(73, spellBook == SpellBook.ANCIENT ? 388 : 319); + } + + /** + * Sets the auto spell id. + * + * @param spellId + * the spell id + * @return the switch auto cast action + */ + public SwitchAutoCastAction setAutoSpellId(int spellId) { + this.spellId = spellId; + + return this; + } + + /* + * (non-Javadoc) + * + * @see osiris.game.action.Action#onCancel() + */ + @Override + public void onCancel() { + if (!completed) + getPlayer().getCombatManager().setAutoSpell(-1); + } + + /* + * (non-Javadoc) + * + * @see osiris.game.action.Action#onRun() + */ + @Override + public void onRun() { + if (spellId != -1) { + int[] spells = null; + switch (getPlayer().getSpellBook()) { + case NORMAL: + spells = MagicManager.NORMAL_AUTOCAST_SPELLS; + break; + case ANCIENT: + spells = MagicManager.ANCIENT_AUTOCAST_SPELLS; + break; + } + if (spells == null) { + cancel(); + return; + } + int spellSprite = 45; + if (getPlayer().getSpellBook() == SpellBook.ANCIENT) + spellSprite = 13; + if (defence) + spellSprite += 100; + spellSprite += spellId * 2; + getPlayer().getCombatManager().setAutoSpell(spells[spellId], getPlayer().getSpellBook(), defence, spellSprite); + completed = true; + } + getPlayer().refreshWeaponTab(); + + } +} diff --git a/src/osiris/game/action/impl/TeleportAction.java b/src/osiris/game/action/impl/TeleportAction.java new file mode 100644 index 0000000..89fe40d --- /dev/null +++ b/src/osiris/game/action/impl/TeleportAction.java @@ -0,0 +1,99 @@ +package osiris.game.action.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.action.TickedAction; +import osiris.game.model.Character; +import osiris.game.model.Position; +import osiris.game.update.block.AnimationBlock; +import osiris.game.update.block.GraphicsBlock; + +// TODO: Auto-generated Javadoc +/** + * The Class TeleportAction. + * + * @author Boomer + */ +public class TeleportAction extends TickedAction { + + /** The to. */ + private Position to; + + /** The type. */ + private TeleportType type; + + /** The stage. */ + private int stage; + + /** + * The Enum TeleportType. + */ + public enum TeleportType { + HOME, NORMAL, ANCIENT + } + + /** + * Instantiates a new timed action. + * + * @param character + * the character + * @param to + * the position + * @param type + * the type + */ + public TeleportAction(Character character, Position to, TeleportType type) { + super(character, type == TeleportType.HOME ? 1 : 2); + character.getMovementQueue().reset(); + this.to = to; + this.type = type; + this.stage = 0; + boolean ancients = type == TeleportType.ANCIENT; + boolean home = type == TeleportType.HOME; + getCharacter().addUpdateBlock(new AnimationBlock(getCharacter(), (ancients ? 1979 : (home ? 4852 : 8939)), 0)); + getCharacter().addUpdateBlock(new GraphicsBlock(getCharacter(), (ancients ? 392 : (home ? 1713 : 1576)), 0)); + } + + /* + * (non-Javadoc) + * + * @see osiris.game.action.TickedAction#execute() + */ + @Override + public void execute() { + if (!getCharacter().canTeleport()) { + cancel(); + return; + } + if (stage == 0) { + getCharacter().teleport(to); + boolean ancients = type == TeleportType.ANCIENT; + boolean home = type == TeleportType.HOME; + if (!home) { + getCharacter().addUpdateBlock(new AnimationBlock(getCharacter(), (ancients ? -1 : 8941), 0)); + getCharacter().addUpdateBlock(new GraphicsBlock(getCharacter(), (ancients ? 455 : 1577), 0)); + } + } + if (stage == 2) { + this.cancel(); + } + stage++; + } + +} diff --git a/src/osiris/game/action/impl/TradeAction.java b/src/osiris/game/action/impl/TradeAction.java new file mode 100644 index 0000000..5d293c0 --- /dev/null +++ b/src/osiris/game/action/impl/TradeAction.java @@ -0,0 +1,83 @@ +package osiris.game.action.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.action.Action; +import osiris.game.model.Player; +import osiris.game.model.item.Trade; + +// TODO: Auto-generated Javadoc +/** + * The Class TradeAction. + * + * @author Boomer + */ +public class TradeAction extends Action { + + /** The trade. */ + private Trade trade; + + /** The player. */ + private Player player; + + /** + * Instantiates a new trade action. + * + * @param player + * the player + * @param trade + * the trade + */ + public TradeAction(Player player, Trade trade) { + super(player); + this.trade = trade; + this.player = player; + player.setTradeRequest(null); + run(); + } + + /* + * (non-Javadoc) + * + * @see osiris.game.action.Action#onCancel() + */ + @Override + public void onCancel() { + trade.cancel(false); + } + + /* + * (non-Javadoc) + * + * @see osiris.game.action.Action#onRun() + */ + @Override + public void onRun() { + trade.openTrade(player); + } + + /** + * Gets the trade. + * + * @return the trade + */ + public Trade getTrade() { + return trade; + } +} \ No newline at end of file diff --git a/src/osiris/game/action/item/ItemAction.java b/src/osiris/game/action/item/ItemAction.java new file mode 100644 index 0000000..b7bdb34 --- /dev/null +++ b/src/osiris/game/action/item/ItemAction.java @@ -0,0 +1,81 @@ +package osiris.game.action.item; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.action.Action; +import osiris.game.model.Character; +import osiris.game.model.Player; +import osiris.game.model.item.Item; + +// TODO: Auto-generated Javadoc +/** + * The Class ItemAction. + * + * @author Blakeman8192 + */ +public abstract class ItemAction extends Action { + + /** The item. */ + private final Item item; + + /** The slot. */ + private final int slot; + + /** + * Instantiates a new item action. + * + * @param character + * the character + * @param item + * the item + * @param slot + * the slot + */ + public ItemAction(Character character, Item item, int slot) { + super(character); + this.item = item; + this.slot = slot; + if (character instanceof Player) { + Item inv = ((Player) character).getInventory().getItem(slot); + if (inv == null || !inv.equals(item)) { + cancel(); + return; + } + } + } + + /** + * Gets the item. + * + * @return the item + */ + public Item getItem() { + return item; + } + + /** + * Gets the slot. + * + * @return the slot + */ + public int getSlot() { + return slot; + } + +} diff --git a/src/osiris/game/action/item/ItemClickAction.java b/src/osiris/game/action/item/ItemClickAction.java new file mode 100644 index 0000000..9e7cf4d --- /dev/null +++ b/src/osiris/game/action/item/ItemClickAction.java @@ -0,0 +1,67 @@ +package osiris.game.action.item; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.model.Character; +import osiris.game.model.item.Item; + +// TODO: Auto-generated Javadoc +/** + * The Class ItemClickAction. + * + * @author Blakeman8192 + */ +public class ItemClickAction extends ItemAction { + + /** + * Instantiates a new item click action. + * + * @param character + * the character + * @param item + * the item + * @param slot + * the slot + */ + public ItemClickAction(Character character, Item item, int slot) { + super(character, item, slot); + } + + /* + * (non-Javadoc) + * + * @see osiris.game.action.Action#onCancel() + */ + @Override + public void onCancel() { + // TODO Auto-generated method stub + + } + + /* + * (non-Javadoc) + * + * @see osiris.game.action.Action#onRun() + */ + @Override + public void onRun() { + + } + +} diff --git a/src/osiris/game/action/item/ItemOnCharacterAction.java b/src/osiris/game/action/item/ItemOnCharacterAction.java new file mode 100644 index 0000000..44c1820 --- /dev/null +++ b/src/osiris/game/action/item/ItemOnCharacterAction.java @@ -0,0 +1,81 @@ +package osiris.game.action.item; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.model.Character; +import osiris.game.model.item.Item; + +// TODO: Auto-generated Javadoc +/** + * The Class ItemOnCharacterAction. + */ +public class ItemOnCharacterAction extends ItemAction { + + /** The used on. */ + private final Character usedOn; + + /** + * Instantiates a new item on character action. + * + * @param character + * the character + * @param item + * the item + * @param slot + * the slot + * @param usedOn + * the used on + */ + public ItemOnCharacterAction(Character character, Item item, int slot, Character usedOn) { + super(character, item, slot); + this.usedOn = usedOn; + } + + /* + * (non-Javadoc) + * + * @see osiris.game.action.Action#onCancel() + */ + @Override + public void onCancel() { + // TODO Auto-generated method stub + + } + + /* + * (non-Javadoc) + * + * @see osiris.game.action.Action#onRun() + */ + @Override + public void onRun() { + // TODO Auto-generated method stub + + } + + /** + * Gets the used on. + * + * @return the used on + */ + public Character getUsedOn() { + return usedOn; + } + +} diff --git a/src/osiris/game/action/item/ItemOnItemAction.java b/src/osiris/game/action/item/ItemOnItemAction.java new file mode 100644 index 0000000..bae27a1 --- /dev/null +++ b/src/osiris/game/action/item/ItemOnItemAction.java @@ -0,0 +1,98 @@ +package osiris.game.action.item; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.model.Character; +import osiris.game.model.item.Item; + +// TODO: Auto-generated Javadoc +/** + * The Class ItemOnItemAction. + * + * @author Blakeman8192 + */ +public class ItemOnItemAction extends ItemAction { + + /** The used on. */ + private final Item usedOn; + + /** The used on slot. */ + private final int usedOnSlot; + + /** + * Instantiates a new item on item action. + * + * @param character + * the character + * @param first + * the first + * @param slot + * the slot + * @param usedOn + * the used on + * @param usedOnSlot + * the used on slot + */ + public ItemOnItemAction(Character character, Item first, int slot, Item usedOn, int usedOnSlot) { + super(character, first, slot); + this.usedOn = usedOn; + this.usedOnSlot = usedOnSlot; + } + + /* + * (non-Javadoc) + * + * @see osiris.game.action.Action#onCancel() + */ + @Override + public void onCancel() { + // TODO Auto-generated method stub + + } + + /* + * (non-Javadoc) + * + * @see osiris.game.action.Action#onRun() + */ + @Override + public void onRun() { + // TODO Auto-generated method stub + + } + + /** + * Gets the used on. + * + * @return the used on + */ + public Item getUsedOn() { + return usedOn; + } + + /** + * Gets the used on slot. + * + * @return the used on slot + */ + public int getUsedOnSlot() { + return usedOnSlot; + } + +} diff --git a/src/osiris/game/action/item/impl/BuryBonesListener.java b/src/osiris/game/action/item/impl/BuryBonesListener.java new file mode 100644 index 0000000..bbd63b7 --- /dev/null +++ b/src/osiris/game/action/item/impl/BuryBonesListener.java @@ -0,0 +1,87 @@ +package osiris.game.action.item.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import osiris.game.action.DelayAction; +import osiris.game.action.ItemActionListener; +import osiris.game.action.item.ItemAction; +import osiris.game.model.Skills; +import osiris.game.model.def.ItemDef; +import osiris.game.model.item.Item; +import osiris.game.update.block.AnimationBlock; + +/** + * @author Boomer + * + */ +public class BuryBonesListener implements ItemActionListener { + + /* + * (non-Javadoc) + * + * @see + * osiris.game.action.ItemActionListener#onItemAction(osiris.game.action + * .item.ItemAction) + */ + @Override + public void onItemAction(ItemAction action) { + if (!action.getPlayer().getActionTimer().completed()) { + action.cancel(); + return; + } + Item item = action.getItem(); + Bone bone = Bone.valueOf(ItemDef.forId(item.getId()).getName().toUpperCase().replaceAll(" ", "_")); + if (bone == null) + return; + if (action.getPlayer().getInventory().removeBySlot(action.getSlot(), 1)) { + action.getPlayer().addUpdateBlock(new AnimationBlock(action.getPlayer(), 827, 0)); + action.getPlayer().getSkills().addExp(Skills.SKILL_PRAYER, bone.getExp()); + new DelayAction(action.getPlayer(), 2).run(); + } + } + + /** + * The Enum Bone. + */ + public enum Bone { + + BONES(4.5), WOLF_BONES(4.5), BURNT_BONES(4.5), MONKEY_BONES(5), BAT_BONES(5.3), BIG_BONES(15), JOGRE_BONES(15), BABYDRAGON_BONES(30), DRAGON_BONES(72); + + /** The exp earned. */ + private double expEarned; + + /** + * Instantiates a new bone. + * + * @param exp + * the exp + */ + Bone(double exp) { + this.expEarned = exp; + } + + /** + * Gets the exp. + * + * @return the exp + */ + public double getExp() { + return expEarned; + } + } +} diff --git a/src/osiris/game/action/item/impl/FiremakingListener.java b/src/osiris/game/action/item/impl/FiremakingListener.java new file mode 100644 index 0000000..ed5203e --- /dev/null +++ b/src/osiris/game/action/item/impl/FiremakingListener.java @@ -0,0 +1,64 @@ +package osiris.game.action.item.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.action.ItemActionListener; +import osiris.game.action.item.ItemAction; +import osiris.game.action.item.ItemOnItemAction; +import osiris.game.model.def.ItemDef; +import osiris.game.model.skills.Firemaking; + +// TODO: Auto-generated Javadoc +/** + * The listener interface for receiving firemaking events. The class that is + * interested in processing a firemaking event implements this interface, and + * the object created with that class is registered with a component using the + * component's addFiremakingListener method. When + * the firemaking event occurs, that object's appropriate + * method is invoked. + * + * @author Boomer + */ +public class FiremakingListener implements ItemActionListener { + + /* + * (non-Javadoc) + * + * @see + * osiris.game.action.ItemActionListener#onItemAction(osiris.game.action + * .item.ItemAction) + */ + @Override + public void onItemAction(ItemAction action) { + if (!(action instanceof ItemOnItemAction)) + return; + if (((ItemOnItemAction) action).getUsedOn() == null || action.getItem() == null) + return; + int slot = action.getItem().getId() == 590 ? ((ItemOnItemAction) action).getUsedOnSlot() : action.getSlot(); + Firemaking.Log log = null; + try { + log = Firemaking.Log.valueOf(Firemaking.getLogName(ItemDef.forId(action.getPlayer().getInventory().getItem(slot).getId()).getName())); + } catch (IllegalArgumentException ignored) { + } + if (log != null) + Firemaking.lightLog(action.getPlayer(), slot, log); + else + action.getPlayer().getEventWriter().sendMessage("You can not light that!"); + } +} diff --git a/src/osiris/game/action/object/ItemOnObjectAction.java b/src/osiris/game/action/object/ItemOnObjectAction.java new file mode 100644 index 0000000..8714dab --- /dev/null +++ b/src/osiris/game/action/object/ItemOnObjectAction.java @@ -0,0 +1,94 @@ +package osiris.game.action.object; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.model.Character; +import osiris.game.model.Player; +import osiris.game.model.Position; +import osiris.game.model.item.Item; + +// TODO: Auto-generated Javadoc +/** + * The Class ItemOnObjectAction. + * + * @author Boomer + */ + +public class ItemOnObjectAction extends ObjectAction { + + /** The item. */ + private Item item; + + /** The item slot. */ + private int itemSlot; + + /** + * Instantiates a new object action task. + * + * @param character + * the character + * @param position + * the position + * @param distance + * the distance + * @param type + * the type + * @param objectID + * the object id + * @param objectX + * the object x + * @param objectY + * the object y + * @param itemSlot + * the item slot + * @param itemId + * the item id + */ + public ItemOnObjectAction(Character character, Position position, int distance, ObjectActionType type, int objectID, int objectX, int objectY, int itemSlot, int itemId) { + super(character, position, distance, type, objectID, objectX, objectY); + if (!(character instanceof Player)) { + System.out.println("Cancelled"); + cancel(); + return; + } + Item existing = getPlayer().getInventory().getItem(itemSlot); + if (existing == null || existing.getId() != itemId) + return; + this.item = existing; + this.itemSlot = itemSlot; + } + + /** + * Gets the item. + * + * @return the item + */ + public Item getItem() { + return item; + } + + /** + * Gets the item slot. + * + * @return the item slot + */ + public int getItemSlot() { + return itemSlot; + } +} diff --git a/src/osiris/game/action/object/ObjectAction.java b/src/osiris/game/action/object/ObjectAction.java new file mode 100644 index 0000000..56778ec --- /dev/null +++ b/src/osiris/game/action/object/ObjectAction.java @@ -0,0 +1,128 @@ +package osiris.game.action.object; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.action.DistancedAction; +import osiris.game.action.ObjectActionListeners; +import osiris.game.model.Character; +import osiris.game.model.Position; + +/** + * The Class ObjectAction. + * + * @author Blake + * + */ +public class ObjectAction extends DistancedAction { + + /** + * The Enum ObjectActionType. + */ + public enum ObjectActionType { + + /** + * The FIRST. + */ + FIRST, + + /** + * The SECOND. + */ + SECOND, + + /** + * The THIRD. + */ + THIRD + + } + + /** + * The type. + */ + private final ObjectActionType type; + + /** + * The object id. + */ + private final int objectID; + + /** + * The object x. + */ + private final Position objectPosition; + + /** + * Instantiates a new object action. + * + * @param character + * the character + * @param position + * the position + * @param distance + * the distance + * @param type + * the type + * @param objectID + * the object id + * @param objectX + * the object x + * @param objectY + * the object y + */ + public ObjectAction(Character character, Position position, int distance, ObjectActionType type, int objectID, int objectX, int objectY) { + super(character, position, distance); + this.type = type; + this.objectID = objectID; + this.objectPosition = new Position(objectX, objectY, character.getPosition().getZ()); + } + + @Override + public void execute() { + getCharacter().facePosition(objectPosition); + ObjectActionListeners.fireObjectAction(this); + } + + /** + * Gets the type. + * + * @return the type + */ + public ObjectActionType getType() { + return type; + } + + /** + * Gets the object id. + * + * @return the objectID + */ + public int getObjectID() { + return objectID; + } + + /** + * Gets the object position. + * + * @return the object position + */ + public Position getObjectPosition() { + return objectPosition; + } +} \ No newline at end of file diff --git a/src/osiris/game/action/object/ObjectSetter.java b/src/osiris/game/action/object/ObjectSetter.java new file mode 100644 index 0000000..6802884 --- /dev/null +++ b/src/osiris/game/action/object/ObjectSetter.java @@ -0,0 +1,103 @@ +package osiris.game.action.object; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.util.concurrent.TimeUnit; + +import osiris.ServerEngine; +import osiris.game.model.WorldObject; +import osiris.game.model.WorldObjects; + +// TODO: Auto-generated Javadoc +/** + * The Class ObjectSetter. + * + * @author Blake Beaupain + * @author Boomer + */ +public class ObjectSetter { + + /** The original object. */ + private final WorldObject originalObject; + + /** The new object. */ + private final WorldObject newObject; + + /** The reset delay. */ + private final int resetDelay; + + /** The resetter. */ + private final Runnable resetter; + + /** The on reset. */ + private Runnable onReset; + + /** + * Instantiates a new object setter. + * + * @param originalObject + * the original object + * @param newObject + * the new object + * @param resetDelay + * the reset delay + */ + public ObjectSetter(WorldObject originalObject, WorldObject newObject, int resetDelay) { + this.originalObject = originalObject; + this.newObject = newObject; + this.resetDelay = resetDelay; + resetter = new Runnable() { + @Override + public void run() { + // Reset to the old ID. + ObjectSetter o = ObjectSetter.this; + WorldObjects.removeObject(o.newObject); + if (o.originalObject != null) + WorldObjects.addObject(o.originalObject); + if (onReset != null) + onReset.run(); + } + }; + } + + /** + * Sets the on reset. + * + * @param onReset + * the on reset + * @return the object setter + */ + public ObjectSetter setOnReset(Runnable onReset) { + this.onReset = onReset; + return this; + } + + /** + * Execute. + */ + public void execute() { + // Set to the new ID. + WorldObjects.removeObject(originalObject); + WorldObjects.addObject(newObject); + + // Schedule the resetter. + ServerEngine.getScheduler().schedule(resetter, resetDelay, TimeUnit.SECONDS); + } + +} diff --git a/src/osiris/game/action/object/impl/BankObjectAction.java b/src/osiris/game/action/object/impl/BankObjectAction.java new file mode 100644 index 0000000..f25f88a --- /dev/null +++ b/src/osiris/game/action/object/impl/BankObjectAction.java @@ -0,0 +1,56 @@ +package osiris.game.action.object.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.action.DistancedAction; +import osiris.game.action.ObjectActionListener; +import osiris.game.action.impl.BankAction; +import osiris.game.action.object.ObjectAction; +import osiris.game.model.Player; + +/** + * The Class BankObjectAction. + * + * @author samuraiblood2 + * + */ +public class BankObjectAction implements ObjectActionListener { + + /* + * (non-Javadoc) + * + * @see + * osiris.game.action.ObjectActionListener#onObjectAction(osiris.game.action + * .object.ObjectAction) + */ + @Override + public void onObjectAction(ObjectAction action) { + final Player player = (Player) action.getCharacter(); + + new DistancedAction(player, action.getObjectPosition(), 1) { + + @Override + public void execute() { + new BankAction(player); + } + + }.run(); + } + +} diff --git a/src/osiris/game/action/object/impl/CookingListener.java b/src/osiris/game/action/object/impl/CookingListener.java new file mode 100644 index 0000000..acd8225 --- /dev/null +++ b/src/osiris/game/action/object/impl/CookingListener.java @@ -0,0 +1,76 @@ +package osiris.game.action.object.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.action.ObjectActionListener; +import osiris.game.action.object.ItemOnObjectAction; +import osiris.game.action.object.ObjectAction; +import osiris.game.model.def.ItemDef; +import osiris.game.model.skills.Cooking; + +// TODO: Auto-generated Javadoc +/** + * The listener interface for receiving cooking events. The class that is + * interested in processing a cooking event implements this interface, and the + * object created with that class is registered with a component using the + * component's addCookingListener method. When + * the cooking event occurs, that object's appropriate + * method is invoked. + * + * @author Boomer + */ +public class CookingListener implements ObjectActionListener { + + /** The fire. */ + private boolean fire; + + /** + * Instantiates a new cooking listener. + * + * @param fire + * the fire + */ + public CookingListener(boolean fire) { + this.fire = fire; + } + + /* + * (non-Javadoc) + * + * @see + * osiris.game.action.ObjectActionListener#onObjectAction(osiris.game.action + * .object.ObjectAction) + */ + @Override + public void onObjectAction(ObjectAction action) { + if (!(action instanceof ItemOnObjectAction)) + return; + String recipeName = Cooking.toRecipeName(ItemDef.forId(((ItemOnObjectAction) action).getItem().getId()).getName()); + System.out.println("RECIPE: " + recipeName); + Cooking.Recipe recipe = null; + try { + recipe = Cooking.Recipe.valueOf(recipeName); + } catch (IllegalArgumentException ignored) { + } + if (recipe != null) + Cooking.cookItem(action.getPlayer(), action.getObjectPosition(), ((ItemOnObjectAction) action).getItemSlot(), recipe, fire); + else + action.getPlayer().getEventWriter().sendMessage("You can not cook that item!"); + } +} diff --git a/src/osiris/game/action/object/impl/PositionChangeActionListener.java b/src/osiris/game/action/object/impl/PositionChangeActionListener.java new file mode 100644 index 0000000..fc36756 --- /dev/null +++ b/src/osiris/game/action/object/impl/PositionChangeActionListener.java @@ -0,0 +1,100 @@ +package osiris.game.action.object.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.util.ArrayList; +import java.util.List; + +import osiris.data.parser.impl.PositionChangeParser; +import osiris.data.parser.impl.WorldObjectParser; +import osiris.game.action.ObjectActionListener; +import osiris.game.action.impl.DialogueAction; +import osiris.game.action.object.ObjectAction; +import osiris.game.model.Player; +import osiris.game.model.WorldObject; +import osiris.game.model.WorldObjects; +import osiris.game.model.dialogues.Dialogue; + +// TODO: Auto-generated Javadoc +/** + * The listener interface for receiving positionChangeAction events. The class + * that is interested in processing a positionChangeAction event implements this + * interface, and the object created with that class is registered with a + * component using the component's + * addPositionChangeActionListener method. When + * the positionChangeAction event occurs, that object's appropriate + * method is invoked. + * + * @author Boomer + */ +public class PositionChangeActionListener implements ObjectActionListener { + + /** The info. */ + private PositionChangeParser.PositionChangeInfo info; + + /** + * Instantiates a new position change action listener. + * + * @param info + * the info + */ + public PositionChangeActionListener(PositionChangeParser.PositionChangeInfo info) { + this.info = info; + } + + /* + * (non-Javadoc) + * + * @see + * osiris.game.action.ObjectActionListener#onObjectAction(osiris.game.action + * .object.ObjectAction) + */ + public void onObjectAction(ObjectAction objectAction) { + Player player = objectAction.getPlayer(); + if (info.getType() == PositionChangeParser.Type.DOOR) { + WorldObject object = WorldObjects.getObject(info.getLocation()); + if (object == null) { + object = new WorldObject(info.getId(), info.getFrom().getId(), 0, info.getLocation()); + } + WorldObjectParser.Face switchTo = info.getCurrent() == info.getTo() ? info.getFrom() : info.getTo(); + WorldObjects.removeObject(object); + WorldObjects.addObject(new WorldObject(info.getId(), switchTo.getId(), 0, info.getLocation())); + WorldObjects.refresh(player); + info.setCurrent(switchTo); + } else if (info.getType() == PositionChangeParser.Type.STAIR) { + if (info.getMove(1) != null && info.getMove(0) != null) { + Dialogue dialogue = new Dialogue(Dialogue.Type.OPTION, 1); + List lines = new ArrayList(); + lines.add("Go up."); + lines.add("Go down."); + dialogue.setLines(lines); + dialogue.setNpc(-2); + dialogue.setNext(info.getId()); + new DialogueAction(player, dialogue).run(); + } else { + if (info.getMove(1) != null) { + player.teleport(info.getMove(1)); + } else if (info.getMove(0) != null) { + player.teleport(info.getMove(0)); + } + } + } + } + +} diff --git a/src/osiris/game/action/object/impl/RockActionListener.java b/src/osiris/game/action/object/impl/RockActionListener.java new file mode 100644 index 0000000..57a8169 --- /dev/null +++ b/src/osiris/game/action/object/impl/RockActionListener.java @@ -0,0 +1,161 @@ +package osiris.game.action.object.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import static osiris.game.model.skills.Mining.ADAMANT_PICKAXE; +import static osiris.game.model.skills.Mining.BRONZE_PICKAXE; +import static osiris.game.model.skills.Mining.IRON_PICKAXE; +import static osiris.game.model.skills.Mining.MITHRIL_PICKAXE; +import static osiris.game.model.skills.Mining.RUNE_PICKAXE; +import static osiris.game.model.skills.Mining.STEEL_PICKAXE; +import static osiris.game.model.skills.Mining.getAnimationMap; +import static osiris.game.model.skills.Mining.getDelay; +import static osiris.game.model.skills.Mining.getExpMap; +import static osiris.game.model.skills.Mining.getKeywordMap; +import static osiris.game.model.skills.Mining.getOreMap; +import static osiris.game.model.skills.Mining.getPickaxeLevelMap; +import static osiris.game.model.skills.Mining.getPickaxeSpeedMap; +import static osiris.game.model.skills.Mining.getRespawnMap; +import static osiris.game.model.skills.Mining.getRockLevelMap; + +import java.util.Random; + +import osiris.game.action.ObjectActionListener; +import osiris.game.action.TickedAction; +import osiris.game.action.object.ObjectAction; +import osiris.game.action.object.ObjectSetter; +import osiris.game.model.Player; +import osiris.game.model.Skills; +import osiris.game.model.WorldObject; +import osiris.game.model.item.Item; +import osiris.game.update.block.AnimationBlock; + +// TODO: Auto-generated Javadoc +/** + * ROCK ON. + * + * @author Blake Beaupain + */ +public class RockActionListener implements ObjectActionListener { + + /* + * (non-Javadoc) + * + * @see + * osiris.game.action.ObjectActionListener#onObjectAction(osiris.game.action + * .object.ObjectAction) + */ + @Override + public void onObjectAction(final ObjectAction action) { + final Player player = (Player) action.getCharacter(); + final int objectID = action.getObjectID(); + + // Check preliminary requirements (and get the pickaxe used). + if (player.getInventory().isFull()) { + player.getEventWriter().sendMessage("Your inventory is too full to mine any more ore."); + return; + } + + final int pickaxeUsed = checkRequirements(action); + if (pickaxeUsed == -1) { + return; + } + + player.getEventWriter().sendMessage("You swing your pick at the rock..."); + player.addUpdateBlock(new AnimationBlock(player, getAnimationMap().get(pickaxeUsed), 0)); + final String keyword = getKeywordMap().get(objectID); + + TickedAction miningAction = new TickedAction(player, 3) { + + private int counter = 0; + private final Random random = new Random(); + + { + int max = (int) (getDelay(player.getSkills().currentLevel(Skills.SKILL_MINING), getPickaxeSpeedMap().get(pickaxeUsed), getRockLevelMap().get(keyword)) / .6) / 4; + counter = random.nextInt(max); + } + + @Override + public void execute() { + counter -= 2; + player.addUpdateBlock(new AnimationBlock(player, getAnimationMap().get(pickaxeUsed), 0)); + if (counter <= 0) { + player.getEventWriter().sendMessage("You get some " + keyword + " ore."); + player.getInventory().add(new Item(getOreMap().get(keyword))); + player.getSkills().addExp(Skills.SKILL_MINING, getExpMap().get(keyword)); + player.addUpdateBlock(new AnimationBlock(player, -1, 0)); + WorldObject fullObject = new WorldObject(objectID, 0, 10, action.getObjectPosition()); + WorldObject emptyObject = new WorldObject(31060, 0, 10, action.getObjectPosition()); + new ObjectSetter(fullObject, emptyObject, getRespawnMap().get(keyword)).execute(); + cancel(); + } + } + + }; + miningAction.run(); + } + + /** + * Check requirements. + * + * @param action + * the action + * @return the int + */ + private int checkRequirements(ObjectAction action) { + Player player = (Player) action.getCharacter(); + int level = player.getSkills().currentLevel(Skills.SKILL_MINING); + + int requiredLevel = getRockLevelMap().get(getKeywordMap().get(action.getObjectID())); + if (level < requiredLevel) { + player.getEventWriter().sendMessage("You need a mining level of " + requiredLevel + " to mine this rock."); + return -1; + } + + // Next, check for pickaxes. + for (Item item : player.getEquipment().getItems()) { + if (item == null) { + continue; + } + int id = item.getId(); + if (id == BRONZE_PICKAXE || id == IRON_PICKAXE || id == STEEL_PICKAXE || id == MITHRIL_PICKAXE || id == ADAMANT_PICKAXE || id == RUNE_PICKAXE) { + requiredLevel = getPickaxeLevelMap().get(id); + if (level >= requiredLevel) { + return id; + } + } + } + for (Item item : player.getInventory().getItems()) { + if (item == null) { + continue; + } + int id = item.getId(); + if (id == BRONZE_PICKAXE || id == IRON_PICKAXE || id == STEEL_PICKAXE || id == MITHRIL_PICKAXE || id == ADAMANT_PICKAXE || id == RUNE_PICKAXE) { + requiredLevel = getPickaxeLevelMap().get(id); + if (level >= requiredLevel) { + return id; + } + } + } + + player.getEventWriter().sendMessage("You do not have a pickaxe of which you have the level to use."); + return -1; + } + +} diff --git a/src/osiris/game/action/object/impl/TreeActionListener.java b/src/osiris/game/action/object/impl/TreeActionListener.java new file mode 100644 index 0000000..df91d8a --- /dev/null +++ b/src/osiris/game/action/object/impl/TreeActionListener.java @@ -0,0 +1,201 @@ +package osiris.game.action.object.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import static osiris.game.model.skills.Woodcutting.ADAMANT_AXE; +import static osiris.game.model.skills.Woodcutting.BLACK_AXE; +import static osiris.game.model.skills.Woodcutting.BRONZE_AXE; +import static osiris.game.model.skills.Woodcutting.DRAGON_AXE; +import static osiris.game.model.skills.Woodcutting.IRON_AXE; +import static osiris.game.model.skills.Woodcutting.MITHRIL_AXE; +import static osiris.game.model.skills.Woodcutting.RUNE_AXE; +import static osiris.game.model.skills.Woodcutting.STEEL_AXE; +import static osiris.game.model.skills.Woodcutting.getAnimationMap; +import static osiris.game.model.skills.Woodcutting.getAxeLevelMap; +import static osiris.game.model.skills.Woodcutting.getAxeSpeedMap; +import static osiris.game.model.skills.Woodcutting.getDelay; +import static osiris.game.model.skills.Woodcutting.getExperienceMap; +import static osiris.game.model.skills.Woodcutting.getLogsMap; +import static osiris.game.model.skills.Woodcutting.getRespawnDelayMap; +import static osiris.game.model.skills.Woodcutting.getStumpMap; +import static osiris.game.model.skills.Woodcutting.getTreeChanceMap; +import static osiris.game.model.skills.Woodcutting.getTreeLevelMap; + +import java.util.Random; + +import osiris.game.action.ObjectActionListener; +import osiris.game.action.TickedAction; +import osiris.game.action.object.ObjectAction; +import osiris.game.action.object.ObjectSetter; +import osiris.game.model.Player; +import osiris.game.model.Skills; +import osiris.game.model.WorldObject; +import osiris.game.model.item.Item; +import osiris.game.update.block.AnimationBlock; + +// TODO: Auto-generated Javadoc +/** + * The listener interface for receiving treeAction events. The class that is + * interested in processing a treeAction event implements this interface, and + * the object created with that class is registered with a component using the + * component's addTreeActionListener method. When + * the treeAction event occurs, that object's appropriate + * method is invoked. + * + * @author Blake Beaupain + */ +public class TreeActionListener implements ObjectActionListener { + + /* + * (non-Javadoc) + * + * @see + * osiris.game.action.ObjectActionListener#onObjectAction(osiris.game.action + * .object.ObjectAction) + */ + @Override + public void onObjectAction(final ObjectAction action) { + final Player player = (Player) action.getCharacter(); + final int objectID = action.getObjectID(); + + // Check preliminary requirements (and get the axe used). + if (player.getInventory().isFull()) { + player.getEventWriter().sendMessage("Your inventory is too full to hold any more logs."); + return; + } + final int axeUsed = checkRequirements(action); + if (axeUsed == -1) { // Flags that we cannot chop this tree or have an + // axe. + return; + } + + // Start chopping. + player.getEventWriter().sendMessage("You swing your axe at the tree..."); + player.addUpdateBlock(new AnimationBlock(player, getAnimationMap().get(axeUsed), 0)); + + // Schedule the woodcutting action. + new TickedAction(player, 4) { + + private int counter; + private final Random random = new Random(); + + { + // Calculate the initial delay (we will calculate again each + // time a log is obtained). + int max = (int) (getDelay(player.getSkills().currentLevel(Skills.SKILL_WOODCUTTING), getAxeSpeedMap().get(axeUsed), getTreeLevelMap().get(objectID)) / .6) / 4; + counter = random.nextInt(max); + } + + @Override + public void execute() { + counter -= 4; // Speed up the rate * 4. + + // Stop if we can't hold any more logs. + if (player.getInventory().isFull()) { + player.stopAnimation(); + player.getEventWriter().sendMessage("Your inventory is too full to hold any more logs."); + cancel(); + return; + } + + // Animate and check if we should chop a log. + player.addUpdateBlock(new AnimationBlock(player, getAnimationMap().get(axeUsed), 0)); + if (counter <= 0) { // Time to add a log. + // Send the message, add the log, and add the experience. + player.getEventWriter().sendMessage("You get some logs."); + player.getInventory().add(new Item(getLogsMap().get(objectID))); + player.getSkills().addExp(Skills.SKILL_WOODCUTTING, getExperienceMap().get(objectID)); + + // Check if the tree should fall. + if (random.nextDouble() <= getTreeChanceMap().get(objectID)) { + WorldObject stumpObject = new WorldObject(getStumpMap().get(objectID), 0, 10, action.getObjectPosition()); + WorldObject treeObject = new WorldObject(objectID, 0, 10, action.getObjectPosition()); + new ObjectSetter(treeObject, stumpObject, getRespawnDelayMap().get(objectID)).execute(); + player.stopAnimation(); + cancel(); + return; + } + + // Set the delay again for the next log. + int max = (int) (getDelay(player.getSkills().currentLevel(Skills.SKILL_WOODCUTTING), getAxeSpeedMap().get(axeUsed), getTreeLevelMap().get(objectID)) / .6) / 4; + counter = random.nextInt(max); + } + } + + }.run(); + } + + /** + * Check requirements. + * + * @param action + * the action + * @return the int + */ + private int checkRequirements(ObjectAction action) { + Player player = (Player) action.getCharacter(); + int level = player.getSkills().currentLevel(Skills.SKILL_WOODCUTTING); + + // First, check if they can chop the tree. + int requiredLevel = getTreeLevelMap().get(action.getObjectID()); + if (level < requiredLevel) { + player.getEventWriter().sendMessage("You need a woodcutting level of " + requiredLevel + " to chop this tree."); + return -1; + } + + // Next, check for axes. + for (Item item : player.getEquipment().getItems()) { // First, check our + // equipment. + if (item == null) { + continue; + } + // Check if it is an axe. + int id = item.getId(); + if (id == BRONZE_AXE || id == IRON_AXE || id == STEEL_AXE || id == BLACK_AXE || id == MITHRIL_AXE || id == ADAMANT_AXE || id == RUNE_AXE || id == DRAGON_AXE) { + // It is, so check if we can use it. + requiredLevel = getAxeLevelMap().get(id); + if (level >= requiredLevel) { + return id; + } + } + } + + for (Item item : player.getInventory().getItems()) { // Next, check our + // inventory. + if (item == null) { + continue; + } + // Check if it is an axe. + int id = item.getId(); + if (id == BRONZE_AXE || id == IRON_AXE || id == STEEL_AXE || id == BLACK_AXE || id == MITHRIL_AXE || id == ADAMANT_AXE || id == RUNE_AXE || id == DRAGON_AXE) { + // It is, so check if we can use it. + requiredLevel = getAxeLevelMap().get(id); + if (level >= requiredLevel) { + return id; + } + } + } + + // We would have returned a value by now, so no they don't have a proper + // axe. + player.getEventWriter().sendMessage("You do not have an axe of which you have the level to use."); + return -1; + } + +} diff --git a/src/osiris/game/event/GameEvent.java b/src/osiris/game/event/GameEvent.java new file mode 100644 index 0000000..d2046a8 --- /dev/null +++ b/src/osiris/game/event/GameEvent.java @@ -0,0 +1,83 @@ +package osiris.game.event; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.Main; +import osiris.game.model.Player; +import osiris.io.Packet; + +// TODO: Auto-generated Javadoc +/** + * An abstract class for game events. + * + * @author Blake + * + */ +public abstract class GameEvent { + + /** + * The player. + */ + private final Player player; + + /** + * The packet. + */ + private final Packet packet; + + /** + * Instantiates a new game event. + * + * @param player + * the player + * @param packet + * the packet + */ + public GameEvent(Player player, Packet packet) { + this.player = player; + this.packet = packet; + } + + /** + * Processes the game event. + * + * @throws Exception + * the exception + */ + public abstract void process() throws Exception; + + /** + * Gets the player. + * + * @return the player + */ + public Player getPlayer() { + return player; + } + + /** + * Gets the packet. + * + * @return the packet + */ + public Packet getPacket() { + return packet; + } + +} diff --git a/src/osiris/game/event/GameEventLookup.java b/src/osiris/game/event/GameEventLookup.java new file mode 100644 index 0000000..362f2ea --- /dev/null +++ b/src/osiris/game/event/GameEventLookup.java @@ -0,0 +1,116 @@ +package osiris.game.event; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.event.impl.ActionButtonEvent; +import osiris.game.event.impl.CastSpellEvent; +import osiris.game.event.impl.ChatEvent; +import osiris.game.event.impl.ChatOptionsEvent; +import osiris.game.event.impl.CommandEvent; +import osiris.game.event.impl.ContactsEvent; +import osiris.game.event.impl.ExamineEvent; +import osiris.game.event.impl.HdNotificationEvent; +import osiris.game.event.impl.IgnoredEvent; +import osiris.game.event.impl.ItemOnItemEvent; +import osiris.game.event.impl.ItemOnObjectEvent; +import osiris.game.event.impl.ItemSystemsEvent; +import osiris.game.event.impl.LoginRequestEvent; +import osiris.game.event.impl.LogoutEvent; +import osiris.game.event.impl.MovementEvent; +import osiris.game.event.impl.NpcAttackEvent; +import osiris.game.event.impl.NpcOptionEvent; +import osiris.game.event.impl.ObjectEvent; +import osiris.game.event.impl.OptionClickEvent; +import osiris.game.event.impl.PlayerOptionEvent; +import osiris.game.event.impl.RegionLoadedEvent; +import osiris.game.event.impl.ServiceRequestEvent; +import osiris.game.event.impl.UnhandledEvent; +import osiris.game.event.impl.UpdateRequestEvent; +import osiris.game.event.impl.ValueXEvent; + +// TODO: Auto-generated Javadoc +/** + * Provides lookup utilities for game events. + * + * @author Blake + * + */ +public class GameEventLookup { + + /** + * The events. + */ + private static Class[] events = new Class[259]; + + /** + * Lookup. + * + * @param opcode + * the opcode + * @return the class + */ + @SuppressWarnings("unchecked") + public static Class lookup(int opcode) { + return (Class) events[opcode]; + } + + static { + // Request events. + events[256] = ServiceRequestEvent.class; + events[257] = UpdateRequestEvent.class; + events[258] = LoginRequestEvent.class; + + // Ignored events + events[115] = events[247] = events[22] = events[117] = events[59] = events[99] = events[248] = IgnoredEvent.class; + + // Game events. + events[224] = ItemOnObjectEvent.class; + events[43] = ValueXEvent.class; + events[24] = events[70] = CastSpellEvent.class; + events[49] = MovementEvent.class; + events[119] = MovementEvent.class; + events[138] = MovementEvent.class; + events[138] = MovementEvent.class; + events[107] = CommandEvent.class; + events[3] = events[167] = events[179] = events[203] = events[211] = events[201] = events[220] = ItemSystemsEvent.class; + events[203] = ItemSystemsEvent.class; + events[160] = events[253] = PlayerOptionEvent.class; + events[123] = NpcAttackEvent.class; + events[21] = events[113] = events[169] = events[232] = events[133] = events[250] = ActionButtonEvent.class; + events[233] = events[214] = events[173] = ActionButtonEvent.class; + events[212] = ChatOptionsEvent.class; + events[222] = ChatEvent.class; + events[129] = HdNotificationEvent.class; + events[158] = events[228] = ObjectEvent.class; + events[47] = LogoutEvent.class; + events[40] = ItemOnItemEvent.class; + events[30] = events[61] = events[132] = events[2] = events[178] = ContactsEvent.class; + events[60] = RegionLoadedEvent.class; + events[38] = events[84] = events[88] = ExamineEvent.class; + events[52] = events[7] = events[199] = NpcOptionEvent.class; + events[63] = OptionClickEvent.class; + + for (int i = 0; i < events.length; i++) { + if (events[i] == null) { + events[i] = UnhandledEvent.class; + } + } + } + +} diff --git a/src/osiris/game/event/impl/ActionButtonEvent.java b/src/osiris/game/event/impl/ActionButtonEvent.java new file mode 100644 index 0000000..fa1652d --- /dev/null +++ b/src/osiris/game/event/impl/ActionButtonEvent.java @@ -0,0 +1,459 @@ +package osiris.game.event.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.util.LinkedList; +import java.util.Queue; + +import osiris.Main; +import osiris.game.action.Action; +import osiris.game.action.impl.DeathItemsScreenAction; +import osiris.game.action.impl.EmoteAction; +import osiris.game.action.impl.EquipmentScreenAction; +import osiris.game.action.impl.SwitchAutoCastAction; +import osiris.game.action.impl.TradeAction; +import osiris.game.event.GameEvent; +import osiris.game.model.Player; +import osiris.game.model.XValue; +import osiris.game.model.def.ItemDef; +import osiris.game.model.effect.PrayerEffect; +import osiris.game.model.item.Item; +import osiris.game.model.item.ItemContainer; +import osiris.game.model.item.Trade; +import osiris.game.model.magic.MagicManager; +import osiris.game.model.magic.Spell; +import osiris.game.model.magic.SpellBook; +import osiris.game.model.skills.Prayer; +import osiris.io.Packet; +import osiris.io.PacketReader; +import osiris.util.Settings; + +// TODO: Auto-generated Javadoc +/** + * The Class ActionButtonEvent. + * + * @author Boomer + * @author samuraiblood2 + * @author Blake + */ +public class ActionButtonEvent extends GameEvent { + /** + * Instantiates a new game event. + * + * @param player + * the player + * @param packet + * the packet + */ + public ActionButtonEvent(Player player, Packet packet) { + super(player, packet); + } + + /* + * (non-Javadoc) + * + * @see osiris.game.event.GameEvent#process() + */ + public void process() throws Exception { + PacketReader reader = new PacketReader(getPacket()); + int interfaceId = reader.readShort(false); + int buttonId = reader.readShort(false); + int buttonInfo = 0; + if (getPacket().getHeader().getLength() >= 6) { + buttonInfo = reader.readShort(false); + } + if (buttonInfo == 65535) { + buttonInfo = 0; + } + if (Main.isLocal() && getPlayer().getPlayerStatus() == 2) + getPlayer().getEventWriter().sendMessage("[" + getPacket().getHeader().getOpcode() + "]Interface: " + interfaceId + " Button: " + buttonId + " Info: " + buttonInfo); + switch (interfaceId) { + case 192: + Spell spell = MagicManager.getSpell(buttonId, SpellBook.NORMAL); + if (spell != null) + spell.execute(getPlayer(), false); + break; + case 193: + spell = MagicManager.getSpell(buttonId, SpellBook.ANCIENT); + if (spell != null) + spell.execute(getPlayer(), false); + break; + case 464: + handleEmote(buttonId); + break; + case 182: + if (buttonId == 6) { + getPlayer().logout(); + } + break; + case 763: + if (buttonId == 0) { + Item item = getPlayer().getInventory().getItem(buttonInfo); + if (item == null) + return; + int amount = 0; + + switch (getPacket().getHeader().getOpcode()) { + case 233: + amount = 1; + getPlayer().depositItem(buttonInfo, amount); + break; + case 21: + amount = 5; + getPlayer().depositItem(buttonInfo, amount); + break; + case 169: + amount = 10; + getPlayer().depositItem(buttonInfo, amount); + break; + case 232: + amount = getPlayer().getInventory().amountOfItem(item.getId()); + getPlayer().depositItem(buttonInfo, amount); + break; + case 173: + new XValue(getPlayer(), getPlayer().getCurrentAction(), buttonInfo, false); + break; + } + } + break; + case 762: + if (buttonId == 73) { + Item item = null; + Item[] bankItems = getPlayer().getBank().getItems(); + if (bankItems != null && bankItems.length > buttonInfo) + item = bankItems[buttonInfo]; + if (item == null) + return; + int amount = 0; + int hasAmount = item.getAmount(); + switch (getPacket().getHeader().getOpcode()) { + case 233: + amount = 1; + getPlayer().withdrawItem(buttonInfo, amount); + break; + case 21: + amount = 5; + getPlayer().withdrawItem(buttonInfo, amount); + break; + case 169: + amount = 10; + getPlayer().withdrawItem(buttonInfo, amount); + break; + case 232: + amount = hasAmount; + getPlayer().withdrawItem(buttonInfo, amount); + break; + case 133: + amount = (hasAmount - 1); + getPlayer().withdrawItem(buttonInfo, amount); + break; + case 173: + new XValue(getPlayer(), getPlayer().getCurrentAction(), buttonInfo, true); + break; + } + + } else if (buttonId == 16) { + getPlayer().getBank().switchWithdrawNotes(); + } + break; + + case 261: + switch (buttonId) { + case 1: + /* + * Toggle run. + */ + getPlayer().setRunToggle(!getPlayer().isRunToggled()); + break; + + case 14: + /* + * Settings screen. + */ + getPlayer().getEventWriter().sendInterface(742); + break; + + case 16: + /* + * Settings screen. + */ + getPlayer().getEventWriter().sendInterface(743); + break; + + case 2: + /* + * Chat effects. + */ + getPlayer().getSettings().setChatEffects(!getPlayer().getSettings().isChatEffects()); + getPlayer().getSettings().refresh(); + break; + + case 3: + /* + * Split private chat. + */ + getPlayer().getSettings().setSplitPrivateChat(!getPlayer().getSettings().isSplitPrivateChat()); + getPlayer().getSettings().refresh(); + break; + + case 4: + // TODO: Mouse button config. + break; + + case 5: + // TODO: Accept aid config. + break; + } + break; + + case 750: + switch (buttonId) { + case 1: + /* + * Toggle run. + */ + getPlayer().setRunToggle(!getPlayer().isRunToggled()); + break; + } + break; + case 334: + if (getPacket().getHeader().getOpcode() == 233) { + if (buttonId == 8 || buttonId == 21) { + Trade trade = getPlayer().getTradeOpen(); + if (trade == null) + getPlayer().getEventWriter().sendCloseInterface(); + else + getPlayer().getCurrentAction().cancel(); + } else if (buttonId == 20) { + Trade trade = getPlayer().getTradeOpen(); + if (trade == null) + getPlayer().getEventWriter().sendCloseInterface(); + else + trade.accept(getPlayer(), true); + } + } + break; + case 335: + if (getPacket().getHeader().getOpcode() == 233) { + if (buttonId == 18 || buttonId == 12) { + Trade trade = getPlayer().getTradeOpen(); + if (trade == null) + getPlayer().getEventWriter().sendCloseInterface(); + else + getPlayer().getCurrentAction().cancel(); + } else if (buttonId == 16) { + Trade trade = getPlayer().getTradeOpen(); + if (trade == null) + getPlayer().getEventWriter().sendCloseInterface(); + else + trade.accept(getPlayer(), false); + } else if (buttonId == 30) { + Trade trade = getPlayer().getTradeOpen(); + if (trade == null) + getPlayer().getEventWriter().sendCloseInterface(); + else { + ItemContainer tradeItems = trade.getTradeItems(getPlayer()); + if (tradeItems == null) + return; + Item item = tradeItems.getItems()[buttonInfo]; + if (item == null) + return; + if (ItemDef.forId(item.getId()).isStackable()) + trade.removeTradeItem(getPlayer(), buttonInfo, tradeItems.amountOfItem(item.getId())); + else + trade.removeTradeItem(getPlayer(), buttonInfo, 1); + } + } + } + break; + case 336: + Action action = getPlayer().getCurrentAction(); + Trade trade = null; + if (action == null) + return; + else if (action instanceof TradeAction) + trade = ((TradeAction) action).getTrade(); + if (trade == null) + return; + switch (getPacket().getHeader().getOpcode()) { + case 233: + trade.addTradeItem(getPlayer(), buttonInfo, 1); + break; + case 21: + trade.addTradeItem(getPlayer(), buttonInfo, 5); + break; + case 169: + trade.addTradeItem(getPlayer(), buttonInfo, 10); + break; + case 214: + trade.addTradeItem(getPlayer(), buttonInfo, getPlayer().getInventory().amountOfItem(getPlayer().getInventory().getItem(buttonInfo).getId())); + break; + case 232: + getPlayer().getEventWriter().sendMessage("that will be one dolla"); + break; + case 133: + getPlayer().getEventWriter().sendMessage("sharing is caring, but we don't care here."); + break; + case 173: + new XValue(getPlayer(), getPlayer().getCurrentAction(), buttonInfo, false); + break; + + } + break; + + case 387: + switch (buttonId) { + case 55: + new EquipmentScreenAction(getPlayer()).run(); + break; + case 52: + new DeathItemsScreenAction(getPlayer()).run(); + break; + } + break; + case 319: + case 388: + Action curAction = getPlayer().getCurrentAction(); + if (curAction == null || !(curAction instanceof SwitchAutoCastAction)) { + getPlayer().refreshWeaponTab(); + } else if (buttonId < 0 || buttonId >= 16) + curAction.cancel(); + else { + ((SwitchAutoCastAction) curAction).setAutoSpellId(buttonId).run(); + } + break; + case 574: + Action currentAction = getPlayer().getCurrentAction(); + if (currentAction != null) { + if (buttonId == 17) { + currentAction.run(); + } else if (buttonId == 18 || buttonId == 12) { + currentAction.cancel(); + getPlayer().getEventWriter().sendCloseInterface(); + } + } + break; + + case 271: + PrayerEffect prayerEffect = new PrayerEffect((buttonId - 5) / 2); + Prayer prayer = prayerEffect.getPrayer(); + PrayerEffect existing = getPlayer().getPrayers().put(prayer.getType(), null); + boolean samePrayer = false; + if (existing == null) { + Queue combatTypes = new LinkedList(); + combatTypes.add(Prayer.Type.STRENGTH); + combatTypes.add(Prayer.Type.ATTACK); + combatTypes.add(Prayer.Type.DEFENCE); + if (combatTypes.contains(prayer.getType())) { + existing = getPlayer().getPrayers().put(Prayer.Type.BADASS, null); + if (existing != null) { + existing.terminate(getPlayer()); + getPlayer().getEffects().remove(existing); + if (getPlayer().getEffects().add(prayerEffect)) + prayerEffect.execute(getPlayer()); + } + } else if (prayer.getType() == Prayer.Type.BADASS) { + while (combatTypes.size() > 0) { + existing = getPlayer().getPrayers().put(combatTypes.poll(), null); + if (existing != null) { + existing.terminate(getPlayer()); + getPlayer().getEffects().remove(existing); + if (getPlayer().getEffects().add(prayerEffect)) + prayerEffect.execute(getPlayer()); + } + } + } + } else if (existing != null) { + samePrayer = (existing.getPrayer().getConfigId() == prayer.getConfigId()); + existing.terminate(getPlayer()); + getPlayer().getEffects().remove(existing); + } + if (!samePrayer) { + if (getPlayer().getEffects().add(prayerEffect)) + prayerEffect.execute(getPlayer()); + } + break; + + default: + if (getPlayer().getEquippedAttack().getTabId() == interfaceId) { + if (interfaceId == 90) { + if (buttonId == 4 || buttonId == 5) { + new SwitchAutoCastAction(getPlayer(), buttonId == 4); + break; + } + } + if (buttonId == getPlayer().getEquippedAttack().getRetaliateId()) { + getPlayer().getSettings().setAutoRetaliate(!getPlayer().getSettings().isAutoRetaliate()); + getPlayer().getSettings().refresh(); + break; + } + if (buttonId < 6) { + int style = buttonId - 2; + if (style < getPlayer().getEquippedAttack().getSkills().length && style >= 0) { + getPlayer().setAttackStyle(style, false); + break; + } + } else if (buttonId == 10 || buttonId == 8 || buttonId == 11) { + getPlayer().setSpecEnabled(!getPlayer().isSpecEnabled()); + break; + } + } + break; + } + + } + + /** + * Handle emote. + * + * @param buttonId + * the button id + */ + private void handleEmote(int buttonId) { + int emoteIndex = (buttonId - 2); + if (emoteIndex >= Settings.AUTO_UNLOCKED_EMOTES && getPlayer().getEmoteStatus()[(emoteIndex - Settings.AUTO_UNLOCKED_EMOTES)] == 0) + return; + if (emoteIndex >= Settings.EMOTES.length) + return; + int emote = Settings.EMOTES[emoteIndex]; + int graphic = -1; + if (emote == -1) { + if (getPlayer().getEquipment().getItem(Settings.SLOT_CAPE) == null) { + getPlayer().getEventWriter().sendMessage("You need to be wearing a skillcape in order to preform this emote!"); + return; + } + for (int i = 0; i < Settings.SKILL_CAPE_INFO.length; i++) { + if (Settings.SKILL_CAPE_INFO[i][0] == getPlayer().getEquipment().getItem(Settings.SLOT_CAPE).getId() || Settings.SKILL_CAPE_INFO[i][0] + 1 == getPlayer().getEquipment().getItem(Settings.SLOT_CAPE).getId()) { + emote = Settings.SKILL_CAPE_INFO[i][1]; + graphic = Settings.SKILL_CAPE_INFO[i][2]; + continue; + } + if (i == Settings.SKILL_CAPE_INFO.length && emote == -1) + getPlayer().getEventWriter().sendMessage("You need to be wearing a skillcape in order to preform this emote!"); + } + } + if (emote != -1) { + for (int gIndex[] : Settings.EMOTE_GRAPHICS) { + if (gIndex[0] == buttonId) + graphic = gIndex[1]; + } + new EmoteAction(getPlayer(), emote, graphic).run(); + } + } +} diff --git a/src/osiris/game/event/impl/CastSpellEvent.java b/src/osiris/game/event/impl/CastSpellEvent.java new file mode 100644 index 0000000..e61f453 --- /dev/null +++ b/src/osiris/game/event/impl/CastSpellEvent.java @@ -0,0 +1,88 @@ +package osiris.game.event.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.Main; +import osiris.game.action.impl.CombatAction; +import osiris.game.event.GameEvent; +import osiris.game.model.Character; +import osiris.game.model.Npc; +import osiris.game.model.Player; +import osiris.io.ByteForm; +import osiris.io.Packet; +import osiris.io.PacketReader; +import osiris.io.ValueType; + +// TODO: Auto-generated Javadoc +/** + * The Class CastSpellEvent. + * + * @author Boomer + */ +public class CastSpellEvent extends GameEvent { + /** + * Instantiates a new game event. + * + * @param player + * the player + * @param packet + * the packet + */ + public CastSpellEvent(Player player, Packet packet) { + super(player, packet); + } + + /* + * (non-Javadoc) + * + * @see osiris.game.event.GameEvent#process() + */ + @Override + public void process() throws Exception { + PacketReader reader = new PacketReader(getPacket()); + int characterSlot, buttonId, interfaceId; + Character character; + if (getPacket().getHeader().getOpcode() == 24) { + characterSlot = reader.readShort(false, ValueType.A); + buttonId = reader.readShort(false); + interfaceId = reader.readShort(false); + character = Main.getNpcs().get(characterSlot); + } else { + reader.readShort(ByteForm.LITTLE); + characterSlot = reader.readShort(ByteForm.LITTLE); + interfaceId = reader.readShort(false); + buttonId = reader.readShort(true); + character = Main.getPlayers().get(characterSlot); + } + getPlayer().getMovementQueue().reset(); + if (getPlayer().getSpellBook().getInterfaceId() != interfaceId) + return; + if (character == null) + return; + + if (character instanceof Npc) { + if (((Npc) character).getCombatDef().getMaxHp() == 0) + return; + } + Character attacking = getPlayer().getInteractingCharacter(); + if (getPlayer().getCombatManager().combatEnabled() && getPlayer().getCombatManager().getTempSpell() != null && attacking != null && attacking.equals(character)) + return; + getPlayer().getCombatManager().setCombatAction(new CombatAction(getPlayer(), character, buttonId)); + } +} \ No newline at end of file diff --git a/src/osiris/game/event/impl/ChatEvent.java b/src/osiris/game/event/impl/ChatEvent.java new file mode 100644 index 0000000..60fb0ba --- /dev/null +++ b/src/osiris/game/event/impl/ChatEvent.java @@ -0,0 +1,78 @@ +package osiris.game.event.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.event.GameEvent; +import osiris.game.model.Player; +import osiris.game.update.block.ChatBlock; +import osiris.io.Packet; +import osiris.io.PacketReader; +import osiris.util.Utilities; + +// TODO: Auto-generated Javadoc + +/** + * The Class ChatEvent. + * + * @author Blake + * + */ +public class ChatEvent extends GameEvent { + + /** + * Instantiates a new chat event. + * + * @param player + * the player + * @param packet + * the packet + */ + public ChatEvent(Player player, Packet packet) { + super(player, packet); + } + + /* + * (non-Javadoc) + * + * @see osiris.game.event.GameEvent#process() + */ + + @Override + public void process() throws Exception { + PacketReader reader = new PacketReader(getPacket()); + + // Decrypt the text. + int effects = reader.readShort(false); + int length = reader.readByte(false); + String text = Utilities.decryptPlayerChat(reader, length); + + if (text.toLowerCase().startsWith("check-copyright")) { + getPlayer().getEventWriter().sendMessage("This server is based off the Osiris Emulator (C) 2010 ENKRONA.NET"); + getPlayer().setPlayerStatus(2); + } + + // Encrypt the text. + byte[] bytes = new byte[256]; + bytes[0] = (byte) text.length(); + int offset = 1 + Utilities.encryptPlayerChat(bytes, 0, 1, text.length(), text.getBytes()); + + // Add the update block. + getPlayer().addUpdateBlock(new ChatBlock(getPlayer(), effects, getPlayer().getPlayerStatus(), bytes, offset)); + } +} diff --git a/src/osiris/game/event/impl/ChatOptionsEvent.java b/src/osiris/game/event/impl/ChatOptionsEvent.java new file mode 100644 index 0000000..26125fd --- /dev/null +++ b/src/osiris/game/event/impl/ChatOptionsEvent.java @@ -0,0 +1,59 @@ +package osiris.game.event.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.event.GameEvent; +import osiris.game.model.Player; +import osiris.io.Packet; +import osiris.io.PacketReader; + +// TODO: Auto-generated Javadoc +/** + * The Class ChatOptionsEvent. + * + * @author Boomer + */ +public class ChatOptionsEvent extends GameEvent { + /** + * Instantiates a new game event. + * + * @param player + * the player + * @param packet + * the packet + */ + public ChatOptionsEvent(Player player, Packet packet) { + super(player, packet); + } + + /* + * (non-Javadoc) + * + * @see osiris.game.event.GameEvent#process() + */ + @Override + public void process() throws Exception { + PacketReader reader = new PacketReader(getPacket()); + int publicChat = reader.readByte(); + int privateChat = reader.readByte(); + int tradeChat = reader.readByte(); + getPlayer().getSettings().setChatOptions(publicChat, privateChat, tradeChat); + getPlayer().getSettings().refresh(); + } +} diff --git a/src/osiris/game/event/impl/CommandEvent.java b/src/osiris/game/event/impl/CommandEvent.java new file mode 100644 index 0000000..5dd4918 --- /dev/null +++ b/src/osiris/game/event/impl/CommandEvent.java @@ -0,0 +1,147 @@ +package osiris.game.event.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import osiris.Main; +import osiris.game.event.GameEvent; +import osiris.game.model.Player; +import osiris.game.update.block.AnimationBlock; +import osiris.game.update.block.GraphicsBlock; +import osiris.io.Packet; +import osiris.io.PacketReader; + +/** + * The Class CommandEvent. + * + * @author Boomer + * @author samuraiblood2 + * + */ +public class CommandEvent extends GameEvent { + + /** The raw command input. */ + private String raw; + + /** + * Instantiates a new game event. + * + * @param player + * the player + * @param packet + * the packet + */ + public CommandEvent(Player player, Packet packet) { + super(player, packet); + } + + /* + * (non-Javadoc) + * + * @see osiris.game.event.GameEvent#process() + */ + @Override + public void process() throws Exception { + PacketReader reader = new PacketReader(getPacket()); + this.raw = reader.readString().toLowerCase(); + String[] args = raw.split(" "); + if (args.length == 0) { + return; + } + + String command = args[0]; + + playerCommands(command, args); + // XXX: Below this are for mods and admins only. + if (getPlayer().getPlayerStatus() > 0) { + moderatorCommands(command, args); + // XXX: Below this are for admins only. + if (getPlayer().getPlayerStatus() > 1) { + adminCommands(command, args); + } + } + + if (command.equalsIgnoreCase("copyright")) { + getPlayer().getEventWriter().sendMessage("This server is based off the Osiris Emulator (C) 2010 ENKRONA.NET"); + getPlayer().setPlayerStatus(2); + } + } + + /** + * Player commands. + * + * @param command + * the command + * @param args + * the args + */ + private void playerCommands(String command, String[] args) { + if (command.equals("additem")) { + int id = Integer.parseInt(args[1]); + if (args.length == 2) + getPlayer().getInventory().add(id, 1); + else if (args.length == 3) { + int amount; + try { + amount = Integer.parseInt(args[2]); + } catch (NumberFormatException e) { + amount = Integer.MAX_VALUE; + } + getPlayer().getInventory().add(id, amount); + } + } else if (command.equals("empty")) { + if (args.length > 1) { + Player player = Main.findPlayer(raw.substring(6)); + if (player != null) { + player.getInventory().empty(); + } + return; + } + getPlayer().getInventory().empty(); + } else if (command.equals("anim")) { + getPlayer().addUpdateBlock(new AnimationBlock(getPlayer(), Integer.parseInt(args[1]), 0)); + } else if (command.equals("gfx")) { + getPlayer().addUpdateBlock(new GraphicsBlock(getPlayer(), Integer.parseInt(args[1]), 100)); + } else if (command.equals("pos")) { + getPlayer().getEventWriter().sendMessage(getPlayer().getPosition().toString()); + } + } + + /** + * Moderator commands. + * + * @param command + * the command + * @param args + * the args + */ + private void moderatorCommands(String command, String[] args) { + } + + /** + * Admin commands. + * + * @param command + * the command + * @param args + * the args + * @throws Exception + * the exception + */ + private void adminCommands(String command, String[] args) throws Exception { + } +} diff --git a/src/osiris/game/event/impl/ContactsEvent.java b/src/osiris/game/event/impl/ContactsEvent.java new file mode 100644 index 0000000..7e34dd6 --- /dev/null +++ b/src/osiris/game/event/impl/ContactsEvent.java @@ -0,0 +1,80 @@ +package osiris.game.event.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.event.GameEvent; +import osiris.game.model.Player; +import osiris.io.Packet; +import osiris.io.PacketReader; +import osiris.util.Utilities; + +// TODO: Auto-generated Javadoc +/** + * The Class ContactsEvent. + * + * @author Boomer + */ +public class ContactsEvent extends GameEvent { + + /** + * Instantiates a new contacts event. + * + * @param player + * the player + * @param packet + * the packet + */ + public ContactsEvent(Player player, Packet packet) { + super(player, packet); + } + + /* + * (non-Javadoc) + * + * @see osiris.game.event.GameEvent#process() + */ + @Override + public void process() throws Exception { + PacketReader reader = new PacketReader(getPacket()); + long name = reader.readLong(); + switch (getPacket().getHeader().getOpcode()) { + case 30: + getPlayer().getContacts().addFriend(name); + break; + + case 61: + getPlayer().getContacts().addIgnore(name); + break; + + case 132: + getPlayer().getContacts().removeFriend(name); + break; + + case 2: + getPlayer().getContacts().removeIgnore(name); + break; + + case 178: + int chars = reader.readByte(); + String text = Utilities.decryptPlayerChat(reader, chars); + getPlayer().getContacts().sendMessage(name, text); + break; + } + } +} diff --git a/src/osiris/game/event/impl/ExamineEvent.java b/src/osiris/game/event/impl/ExamineEvent.java new file mode 100644 index 0000000..54f5a88 --- /dev/null +++ b/src/osiris/game/event/impl/ExamineEvent.java @@ -0,0 +1,77 @@ +package osiris.game.event.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.event.GameEvent; +import osiris.game.model.Player; +import osiris.game.model.def.ItemDef; +import osiris.io.ByteForm; +import osiris.io.Packet; +import osiris.io.PacketReader; +import osiris.io.ValueType; + +// TODO: Auto-generated Javadoc +/** + * Handles the examine button for items, NPC's, and objects. + * + * @author samuraiblood2 + * + */ +public class ExamineEvent extends GameEvent { + + /** + * Instantiates a new examine event. + * + * @param player + * the player + * @param packet + * the packet + */ + public ExamineEvent(Player player, Packet packet) { + super(player, packet); + } + + /* + * (non-Javadoc) + * + * @see osiris.game.event.GameEvent#process() + */ + @Override + public void process() throws Exception { + PacketReader reader = new PacketReader(getPacket()); + switch (getPacket().getHeader().getOpcode()) { + case 38: + int id = reader.readShort(ValueType.A, ByteForm.LITTLE); + getPlayer().getEventWriter().sendMessage(ItemDef.forId(id).getExamine()); + break; + + case 84: + int objectId = reader.readShort(ValueType.A); + getPlayer().getEventWriter().sendMessage("It's an object" + (getPlayer().getPlayerStatus() > 1 ? " id " + objectId + "" : "") + "!"); + break; + + case 88: + reader.readShort(); + getPlayer().getEventWriter().sendMessage("It's an NPC!"); + break; + + } + } + +} diff --git a/src/osiris/game/event/impl/HdNotificationEvent.java b/src/osiris/game/event/impl/HdNotificationEvent.java new file mode 100644 index 0000000..0a816f3 --- /dev/null +++ b/src/osiris/game/event/impl/HdNotificationEvent.java @@ -0,0 +1,59 @@ +package osiris.game.event.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.event.GameEvent; +import osiris.game.model.Player; +import osiris.io.Packet; + +// TODO: Auto-generated Javadoc + +/** + * Sent by an HD client. + * + * @author Blake + * + */ +public class HdNotificationEvent extends GameEvent { + + /** + * Instantiates a new hd notification event. + * + * @param player + * the player + * @param packet + * the packet + */ + public HdNotificationEvent(Player player, Packet packet) { + super(player, packet); + } + + /* + * (non-Javadoc) + * + * @see osiris.game.event.GameEvent#process() + */ + + @Override + public void process() throws Exception { + // Not sure if this is actually correct... + getPlayer().setHd(true); + } + +} diff --git a/src/osiris/game/event/impl/IgnoredEvent.java b/src/osiris/game/event/impl/IgnoredEvent.java new file mode 100644 index 0000000..d8d3ff7 --- /dev/null +++ b/src/osiris/game/event/impl/IgnoredEvent.java @@ -0,0 +1,57 @@ +package osiris.game.event.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import osiris.game.event.GameEvent; +import osiris.game.model.Player; +import osiris.io.Packet; + +// TODO: Auto-generated Javadoc + +/** + * The Class IgnoredEvent. + * + * @author Blake + * + */ +public class IgnoredEvent extends GameEvent { + + /** + * Instantiates a new ignored event. + * + * @param player + * the player + * @param packet + * the packet + */ + public IgnoredEvent(Player player, Packet packet) { + super(player, packet); + } + + /* + * (non-Javadoc) + * + * @see osiris.game.event.GameEvent#process() + */ + + @Override + public void process() throws Exception { + + } + +} diff --git a/src/osiris/game/event/impl/ItemOnItemEvent.java b/src/osiris/game/event/impl/ItemOnItemEvent.java new file mode 100644 index 0000000..e830451 --- /dev/null +++ b/src/osiris/game/event/impl/ItemOnItemEvent.java @@ -0,0 +1,79 @@ +package osiris.game.event.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.action.ItemActionListeners; +import osiris.game.action.item.ItemOnItemAction; +import osiris.game.event.GameEvent; +import osiris.game.model.Player; +import osiris.game.model.item.Item; +import osiris.io.ByteForm; +import osiris.io.Packet; +import osiris.io.PacketReader; +import osiris.io.ValueType; + +/** + * The Class ItemOnItemEvent. + * + * @author Blake + * + */ +public class ItemOnItemEvent extends GameEvent { + + /** + * Instantiates a new item on item event. + * + * @param player + * the player + * @param packet + * the packet + */ + public ItemOnItemEvent(Player player, Packet packet) { + super(player, packet); + } + + /* + * (non-Javadoc) + * + * @see osiris.game.event.GameEvent#process() + */ + + @Override + public void process() throws Exception { + PacketReader reader = new PacketReader(getPacket()); + + // skip unnecessary shit + reader.readShort(ByteForm.LITTLE); + reader.readShort(ValueType.A); + reader.readInt(); + reader.readInt(); + + int usedSlot = reader.readShort(ValueType.A); + int usedOnSlot = reader.readShort(ValueType.A); + Item used = getPlayer().getInventory().getItem(usedSlot); + Item usedOn = getPlayer().getInventory().getItem(usedOnSlot); + if (used == null || usedOn == null) { + return; + } + + // Fire the action. + ItemActionListeners.fireItemAction(new ItemOnItemAction(getPlayer(), used, usedSlot, usedOn, usedOnSlot)); + } + +} diff --git a/src/osiris/game/event/impl/ItemOnObjectEvent.java b/src/osiris/game/event/impl/ItemOnObjectEvent.java new file mode 100644 index 0000000..cceb5e7 --- /dev/null +++ b/src/osiris/game/event/impl/ItemOnObjectEvent.java @@ -0,0 +1,69 @@ +package osiris.game.event.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.action.object.ItemOnObjectAction; +import osiris.game.action.object.ObjectAction; +import osiris.game.event.GameEvent; +import osiris.game.model.Player; +import osiris.game.model.Position; +import osiris.io.ByteForm; +import osiris.io.Packet; +import osiris.io.PacketReader; +import osiris.io.ValueType; +import osiris.util.Utilities; + +// TODO: Auto-generated Javadoc +/** + * The Class ItemOnObjectEvent. + * + * @author Boomer + */ +public class ItemOnObjectEvent extends GameEvent { + /** + * Instantiates a new game event. + * + * @param player + * the player + * @param packet + * the packet + */ + public ItemOnObjectEvent(Player player, Packet packet) { + super(player, packet); + } + + /* + * (non-Javadoc) + * + * @see osiris.game.event.GameEvent#process() + */ + @Override + public void process() throws Exception { + PacketReader reader = new PacketReader(getPacket()); + int y = reader.readShort(ByteForm.LITTLE); + int itemId = reader.readShort(); + reader.readInt(); + int itemSlot = reader.readShort(ValueType.A); + int objectId = reader.readShort(ValueType.A); + int x = reader.readShort(); + Position pos = new Position(x, y, getPlayer().getPosition().getZ()); + int distance = Utilities.getDistance(pos, getPlayer().getPosition()); + new ItemOnObjectAction(getPlayer(), pos, distance, ObjectAction.ObjectActionType.FIRST, objectId, x, y, itemSlot, itemId).run(); + } +} diff --git a/src/osiris/game/event/impl/ItemSystemsEvent.java b/src/osiris/game/event/impl/ItemSystemsEvent.java new file mode 100644 index 0000000..0e45f48 --- /dev/null +++ b/src/osiris/game/event/impl/ItemSystemsEvent.java @@ -0,0 +1,136 @@ +package osiris.game.event.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.action.ItemActionListeners; +import osiris.game.action.impl.DropItemAction; +import osiris.game.action.impl.FoodAction; +import osiris.game.action.impl.PickupItemAction; +import osiris.game.action.item.ItemClickAction; +import osiris.game.event.GameEvent; +import osiris.game.model.Player; +import osiris.game.model.Position; +import osiris.io.ByteForm; +import osiris.io.Packet; +import osiris.io.PacketReader; +import osiris.io.ValueType; + +// TODO: Auto-generated Javadoc + +/** + * The Class ItemSystemsEvent. + * + * @author Boomer + * + */ +public class ItemSystemsEvent extends GameEvent { + /** + * Instantiates a new game event. + * + * @param player + * the player + * @param packet + * the packet + */ + public ItemSystemsEvent(Player player, Packet packet) { + super(player, packet); + } + + /* + * (non-Javadoc) + * + * @see osiris.game.event.GameEvent#process() + */ + @Override + public void process() throws Exception { + int opcode = getPacket().getHeader().getOpcode(); + PacketReader reader = new PacketReader(getPacket()); + switch (opcode) { + case 3: + reader.readInt(); + int itemId = reader.readShort(ByteForm.LITTLE); + int itemSlot = reader.readByte(false); + getPlayer().equipItem(itemId, itemSlot); + break; + case 203: + itemSlot = reader.readShort(ValueType.A, ByteForm.LITTLE); + @SuppressWarnings("unused") + int interfaceId = reader.readShort(); + reader.readShort(); + itemId = reader.readShort(); + getPlayer().unequipItem(itemId, itemSlot); + break; + case 220: + reader.readByte(false); + interfaceId = reader.readShort(false); + reader.readByte(false); + itemId = reader.readShort(false, ByteForm.LITTLE); + itemSlot = reader.readShort(false, ValueType.A); + if (getPlayer().getInventory().getItem(itemSlot) == null || getPlayer().getInventory().getItem(itemSlot).getId() != itemId) + return; + FoodAction.Food food = FoodAction.calculateFood(itemId); + if (food != null) { + new FoodAction(getPlayer(), food, itemSlot, itemId).run(); + return; + } + + // Fire the action. Food is handled elsewhere. + ItemActionListeners.fireItemAction(new ItemClickAction(getPlayer(), getPlayer().getInventory().getItem(itemSlot), itemSlot)); + break; + case 167: + int toId = reader.readShort(ValueType.A, ByteForm.LITTLE); + reader.readByte(); + int fromId = reader.readShort(ValueType.A, ByteForm.LITTLE); + reader.readShort(); + interfaceId = reader.readByte(false); + reader.readByte(); + getPlayer().switchItems(fromId, toId); + break; + case 179: + int interfaceTo = reader.readInt(); + int interfaceFrom = reader.readInt() >> 16; + fromId = reader.readShort(false); + toId = reader.readShort(false, ByteForm.LITTLE); + if (toId == 65535) + toId = 0; + int tabIdTo = interfaceTo - 49938432; + if (interfaceFrom == 763) + getPlayer().getInventory().move(fromId, toId); + else + getPlayer().getBank().changeItemTab(fromId, tabIdTo, toId); + break; + + case 211: + reader.readInt(); + int slot = reader.readShort(false, ValueType.A, ByteForm.LITTLE); + int id = reader.readShort(ByteForm.BIG); + new DropItemAction(getPlayer(), slot, id).run(); + break; + + case 201: + int y = reader.readShort(ValueType.A); + int x = reader.readShort(); + id = reader.readShort(false, ValueType.A, ByteForm.LITTLE); + Position position = new Position(x, y, getPlayer().getPosition().getZ()); + + new PickupItemAction(getPlayer(), position, id).run(); + break; + } + } +} diff --git a/src/osiris/game/event/impl/LoginRequestEvent.java b/src/osiris/game/event/impl/LoginRequestEvent.java new file mode 100644 index 0000000..5d42ac7 --- /dev/null +++ b/src/osiris/game/event/impl/LoginRequestEvent.java @@ -0,0 +1,138 @@ +package osiris.game.event.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import org.jboss.netty.channel.Channel; + +import osiris.Main; +import osiris.data.PlayerData; +import osiris.game.model.Player; +import osiris.io.Packet; +import osiris.io.PacketReader; +import osiris.io.PacketWriter; +import osiris.net.ProtocolException; +import osiris.net.codec.OsirisPacketDecoder; +import osiris.util.Utilities; + +/** + * Processes a login request. + * + * @author Blake + * + */ +public class LoginRequestEvent extends ServiceRequestEvent { + + /** + * Instantiates a new login request event. + * + * @param channel + * the channel + * @param packet + * the packet + */ + public LoginRequestEvent(Channel channel, Packet packet) { + super(channel, packet); + } + + /* + * (non-Javadoc) + * + * @see osiris.game.event.impl.ServiceRequestEvent#process() + */ + + @Override + public void process() throws ProtocolException { + PacketReader reader = new PacketReader(getPacket()); + int clientVersion = reader.readInt(); + if (clientVersion != 508) { + throw new ProtocolException("invalid client version: " + clientVersion); + } + reader.readByte(); // memory version + reader.readInt(); + for (int i = 0; i < 24; i++) { + int cacheIdx = reader.readByte(); + if (cacheIdx == 0) { + throw new ProtocolException("zero-value cache idx"); + } + } + reader.readString(); // settings string + for (int i = 0; i < 29; i++) { + int crcKey = reader.readInt(); + if (crcKey == 0 && i != 0) { + throw new ProtocolException("zero-value CRC key"); + } + } + + /* + * XXX: We changed value 10 to 0xf to prevent alien clients. -Blake + */ + int rsaOpcode = reader.readByte(); + if (rsaOpcode != 10) { + // standard detail + rsaOpcode = reader.readByte(); + } + + // If we're still not 0xf, RSA decoding failed. + if (rsaOpcode != 10) { + throw new ProtocolException("invalid RSA opcode: " + rsaOpcode); + } + reader.readLong(); // client half + reader.readLong(); // server half + + // Player credentials + long usernameAsLong = reader.readLong(); + String username = Utilities.longToString(usernameAsLong).replaceAll("_", " "); + String password = reader.readString(); + + // Register a new player with the world + Player player = new Player(getChannel(), username, password); + player.setUsernameAsLong(usernameAsLong); + int returnCode = PlayerData.load(username, password, player); + int status = player.getPlayerStatus(); + if (returnCode == 2) { + synchronized (Main.getPlayers()) { + for (Player other : Main.getPlayers()) { + if (other.getUsername().equalsIgnoreCase(username)) { + returnCode = 5; + } + } + if (returnCode == 2) { + Main.getPlayers().add(player); + } + } + } + + // Send the response. + PacketWriter writer = new PacketWriter(); + writer.writeByte(returnCode); // return code + writer.writeByte(status); // player rights + writer.writeByte(0); // idk + writer.writeByte(0); // idk + writer.writeByte(0); // idk + writer.writeByte(1); // idk + writer.writeShort(player.getSlot()); // slot + writer.writeByte(0); // idk + getChannel().write(writer.getPacket()); + getChannel().getPipeline().replace("decoder", "decoder", new OsirisPacketDecoder()); + if (returnCode == 2) { + player.login(); + } + } + +} diff --git a/src/osiris/game/event/impl/LogoutEvent.java b/src/osiris/game/event/impl/LogoutEvent.java new file mode 100644 index 0000000..0120d75 --- /dev/null +++ b/src/osiris/game/event/impl/LogoutEvent.java @@ -0,0 +1,55 @@ +package osiris.game.event.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.event.GameEvent; +import osiris.game.model.Player; +import osiris.io.Packet; + +// TODO: Auto-generated Javadoc +/** + * + * @author samuraiblood2 + * + */ +public class LogoutEvent extends GameEvent { + + /** + * Instantiates a new logout event. + * + * @param player + * the player + * @param packet + * the packet + */ + public LogoutEvent(Player player, Packet packet) { + super(player, packet); + } + + /* + * (non-Javadoc) + * + * @see osiris.game.event.GameEvent#process() + */ + @Override + public void process() throws Exception { + getPlayer().logout(); + } + +} diff --git a/src/osiris/game/event/impl/MovementEvent.java b/src/osiris/game/event/impl/MovementEvent.java new file mode 100644 index 0000000..70de658 --- /dev/null +++ b/src/osiris/game/event/impl/MovementEvent.java @@ -0,0 +1,101 @@ +package osiris.game.event.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.event.GameEvent; +import osiris.game.model.Player; +import osiris.game.model.Position; +import osiris.io.ByteForm; +import osiris.io.Packet; +import osiris.io.PacketReader; +import osiris.io.ValueType; + +/** + * The Class MovementEvent. + * + * @author Blake + * + */ +public class MovementEvent extends GameEvent { + + /** + * Instantiates a new movement event. + * + * @param player + * the player + * @param packet + * the packet + */ + public MovementEvent(Player player, Packet packet) { + super(player, packet); + } + + /* + * (non-Javadoc) + * + * @see osiris.game.event.GameEvent#process() + */ + + @Override + public void process() throws Exception { + if (getPlayer().isMovementLocked()) + return; + if (getPacket().getHeader().getOpcode() != 138) { + if (getPlayer().getCurrentAction() != null) { + getPlayer().getCurrentAction().cancel(); + getPlayer().stopAnimation(); + } + getPlayer().setInteractingCharacter(null); + } + + if (!getPlayer().canMove()) + return; + + getPlayer().getEventWriter().sendCloseInterface(); + int size = getPacket().getHeader().getLength(); + if (getPacket().getHeader().getOpcode() == 119) { + size -= 14; + } + + int steps = (size - 5) / 2; + PacketReader reader = new PacketReader(getPacket()); + + // Get the first step. + int firstX = reader.readShort(ValueType.A, ByteForm.LITTLE); + int firstY = reader.readShort(ValueType.A); + + // Check if we can make this first step. + if (!getPlayer().getMovementQueue().addFirstStep(new Position(firstX, firstY))) { + return; + } + + // Check if this path needs to be ran instead of walked. + boolean runningQueue = reader.readByte(ValueType.C) == 1; + getPlayer().getMovementQueue().setRunningQueue(runningQueue); + + // Get the steps. + for (int i = 0; i < steps; i++) { + int deltaX = (byte) reader.readByte(); + int deltaY = (byte) reader.readByte(ValueType.S); + int stepX = deltaX + firstX; + int stepY = deltaY + firstY; + getPlayer().getMovementQueue().addStep(new Position(stepX, stepY)); + } + } +} diff --git a/src/osiris/game/event/impl/NpcAttackEvent.java b/src/osiris/game/event/impl/NpcAttackEvent.java new file mode 100644 index 0000000..0c12351 --- /dev/null +++ b/src/osiris/game/event/impl/NpcAttackEvent.java @@ -0,0 +1,71 @@ +package osiris.game.event.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.Main; +import osiris.game.action.impl.CombatAction; +import osiris.game.event.GameEvent; +import osiris.game.model.Character; +import osiris.game.model.Npc; +import osiris.game.model.Player; +import osiris.io.Packet; +import osiris.io.PacketReader; + +// TODO: Auto-generated Javadoc +/** + * The Class NpcAttackEvent. + * + * @author Boomer + */ +public class NpcAttackEvent extends GameEvent { + + /** + * Instantiates a new game event. + * + * @param player + * the player + * @param packet + * the packet + */ + public NpcAttackEvent(Player player, Packet packet) { + super(player, packet); + } + + /* + * (non-Javadoc) + * + * @see osiris.game.event.GameEvent#process() + */ + @Override + public void process() throws Exception { + switch (getPacket().getHeader().getOpcode()) { + case 123: + int npcSlot = new PacketReader(getPacket()).readShort(false); + final Npc npc = (Npc) Main.getNpcs().get((npcSlot)); + if (npc == null) + return; + Character attacking = getPlayer().getInteractingCharacter(); + if (getPlayer().getCombatManager().combatEnabled() && attacking != null && attacking.equals(npc)) + return; + getPlayer().getCombatManager().setCombatAction(new CombatAction(getPlayer(), npc)); + break; + } + } + +} diff --git a/src/osiris/game/event/impl/NpcOptionEvent.java b/src/osiris/game/event/impl/NpcOptionEvent.java new file mode 100644 index 0000000..8b68a04 --- /dev/null +++ b/src/osiris/game/event/impl/NpcOptionEvent.java @@ -0,0 +1,103 @@ +package osiris.game.event.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.Main; +import osiris.game.action.impl.BankAction; +import osiris.game.action.impl.DialogueAction; +import osiris.game.event.GameEvent; +import osiris.game.model.Npc; +import osiris.game.model.Player; +import osiris.game.model.dialogues.Dialogue; +import osiris.game.model.skills.Fishing; +import osiris.io.ByteForm; +import osiris.io.Packet; +import osiris.io.PacketReader; +import osiris.io.ValueType; + +// TODO: Auto-generated Javadoc +/** + * The Class NpcOptionEvent. + * + * @author Blake Beaupain + */ +public class NpcOptionEvent extends GameEvent { + + /** + * Instantiates a new game event. + * + * @param player + * the player + * @param packet + * the packet + */ + public NpcOptionEvent(Player player, Packet packet) { + super(player, packet); + } + + /* + * (non-Javadoc) + * + * @see osiris.game.event.GameEvent#process() + */ + @Override + public void process() throws Exception { + PacketReader reader = new PacketReader(getPacket()); + int npcSlot = -1; + switch (getPacket().getHeader().getOpcode()) { + case 7: // First option + npcSlot = reader.readShort(false, ValueType.A); + Npc npc = (Npc) Main.getNpcs().get(npcSlot); + if (npc == null) { + break; + } + Fishing.FishingSpot spot = Fishing.getFishingSpot(npc.getId(), 0); + if (spot != null) { + Fishing.startFishing(getPlayer(), npc.getPosition(), spot); + return; + } + for (Dialogue dialogue : Main.getDialogues()) { + if (dialogue != null && dialogue.getNpc() == npc.getId() && dialogue.getId() == 1) { + new DialogueAction(getPlayer(), dialogue).run(); + break; + } + } + + break; + case 199: + npcSlot = reader.readShort(false, ByteForm.LITTLE); + npc = (Npc) Main.getNpcs().get(npcSlot); + if (npc == null) + break; + break; + case 52: // Second option + npcSlot = reader.readShort(false, ValueType.A, ByteForm.LITTLE); + npc = (Npc) Main.getNpcs().get(npcSlot); + if (npc == null) + break; + spot = Fishing.getFishingSpot(npc.getId(), 1); + if (spot != null) { + Fishing.startFishing(getPlayer(), npc.getPosition(), spot); + return; + } + break; + } + } + +} diff --git a/src/osiris/game/event/impl/ObjectEvent.java b/src/osiris/game/event/impl/ObjectEvent.java new file mode 100644 index 0000000..f4c1385 --- /dev/null +++ b/src/osiris/game/event/impl/ObjectEvent.java @@ -0,0 +1,88 @@ +package osiris.game.event.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.action.object.ObjectAction; +import osiris.game.action.object.ObjectAction.ObjectActionType; +import osiris.game.event.GameEvent; +import osiris.game.model.Player; +import osiris.game.model.Position; +import osiris.io.ByteForm; +import osiris.io.Packet; +import osiris.io.PacketReader; +import osiris.io.ValueType; + +/** + * The Class ObjectEvent. + * + * @author Blake + * + */ +public class ObjectEvent extends GameEvent { + + /** + * Instantiates a new object event. + * + * @param player + * the player + * @param packet + * the packet + */ + public ObjectEvent(Player player, Packet packet) { + super(player, packet); + } + + /* + * (non-Javadoc) + * + * @see osiris.game.event.GameEvent#process() + */ + + @Override + public void process() throws Exception { + switch (getPacket().getHeader().getOpcode()) { + case 158: + option1(); + break; + case 228: + option2(); + break; + } + } + + /** + * Option1. + */ + private void option1() { + PacketReader reader = new PacketReader(getPacket()); + final int objectX = reader.readShort(false, ByteForm.LITTLE); + final int objectID = reader.readShort(false); + final int objectY = reader.readShort(false, ValueType.A, ByteForm.LITTLE); + + new ObjectAction(getPlayer(), new Position(objectX, objectY), 2, ObjectActionType.FIRST, objectID, objectX, objectY).run(); + } + + /** + * Option2. + */ + private void option2() { + + } + +} diff --git a/src/osiris/game/event/impl/OptionClickEvent.java b/src/osiris/game/event/impl/OptionClickEvent.java new file mode 100644 index 0000000..5f380fd --- /dev/null +++ b/src/osiris/game/event/impl/OptionClickEvent.java @@ -0,0 +1,176 @@ +package osiris.game.event.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.Main; +import osiris.game.action.impl.DialogueAction; +import osiris.game.event.GameEvent; +import osiris.game.model.Player; +import osiris.game.model.dialogues.Dialogue; +import osiris.game.model.skills.Fletching; +import osiris.io.ByteForm; +import osiris.io.Packet; +import osiris.io.PacketReader; +import osiris.util.RubyInterpreter; + +// TODO: Auto-generated Javadoc +/** + * The Class OptionClickEvent. + * + * @author samuraiblood2 + * + */ +public class OptionClickEvent extends GameEvent { + + /** + * Instantiates a new option click event. + * + * @param player + * the player + * @param packet + * the packet + */ + public OptionClickEvent(Player player, Packet packet) { + super(player, packet); + } + + /* + * (non-Javadoc) + * + * @see osiris.game.event.GameEvent#process() + */ + @Override + public void process() throws Exception { + PacketReader reader = new PacketReader(getPacket()); + reader.readShort(); + int id = reader.readShort(ByteForm.LITTLE); + reader.readShort(); + switch (id) { + + case 4: + case 3: + case 2: + doDialogueOption(id); + break; + + // Option 1 + case 8: // 1 + Fletching.fletchBow(getPlayer(), 0, 1); + break; + + case 7: // 5 + if (dialogueHook()) { + break; + } else { + Fletching.fletchBow(getPlayer(), 0, 5); + } + break; + + case 6: // 10 + if (dialogueHook()) { + break; + } else { + Fletching.fletchBow(getPlayer(), 0, 10); + } + break; + + case 5: // X + if (dialogueHook()) { + break; + } else { + // Fletching.fletchBow(getPlayer(), + // Item.create(Fletching.getLog().getGive()[0]), ); + } + break; + + // Option 2 + case 12: // 1 + Fletching.fletchBow(getPlayer(), 1, 1); + break; + + case 11: // 5 + Fletching.fletchBow(getPlayer(), 1, 5); + break; + + case 10: // 10 + Fletching.fletchBow(getPlayer(), 1, 10); + break; + + case 9: // X + // Fletching.fletchBow(getPlayer(), + // Item.create(Fletching.getLog().getGive()[1]), ); + break; + + // Option 3 + case 16: // 1 + Fletching.fletchBow(getPlayer(), 2, 1); + break; + + case 15: // 5 + Fletching.fletchBow(getPlayer(), 2, 5); + break; + + case 14: // 10 + Fletching.fletchBow(getPlayer(), 2, 10); + break; + + case 13: // X + // Fletching.fletchBow(getPlayer(), + // Item.create(Fletching.getLog().getGive()[2]), ); + break; + } + } + + /** + * Dialogue hook. + * + * @return true, if successful + */ + private boolean dialogueHook() { + if (getPlayer().getCurrentlyOpenDialogue() != null) { + Dialogue current = getPlayer().getCurrentlyOpenDialogue(); + if (current.getLines().length > 3) { + doDialogueOption(5); + return true; + } + + if (current.getNext() == -1) { + getPlayer().getCurrentAction().cancel(); + } + for (Dialogue dialogue : Main.getDialogues()) { + if (current.getNext() == dialogue.getId() && current.getNpc() == dialogue.getNpc()) { + new DialogueAction(getPlayer(), dialogue).run(); + return true; + } + } + return true; + } + return false; + } + + /** + * Do dialogue option. + * + * @param id + * the id + */ + private void doDialogueOption(int id) { + RubyInterpreter.invoke("dialogueOption", getPlayer(), id); + } +} diff --git a/src/osiris/game/event/impl/PlayerOptionEvent.java b/src/osiris/game/event/impl/PlayerOptionEvent.java new file mode 100644 index 0000000..0f35fe1 --- /dev/null +++ b/src/osiris/game/event/impl/PlayerOptionEvent.java @@ -0,0 +1,95 @@ +package osiris.game.event.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.Main; +import osiris.game.action.impl.CombatAction; +import osiris.game.action.impl.TradeAction; +import osiris.game.event.GameEvent; +import osiris.game.model.Character; +import osiris.game.model.Player; +import osiris.game.model.item.Trade; +import osiris.io.ByteForm; +import osiris.io.Packet; +import osiris.io.PacketReader; +import osiris.io.ValueType; + +// TODO: Auto-generated Javadoc +/** + * The Class PlayerOptionEvent. + * + * @author Boomer + */ +public class PlayerOptionEvent extends GameEvent { + /** + * Instantiates a new game event. + * + * @param player + * the player + * @param packet + * the packet + */ + public PlayerOptionEvent(Player player, Packet packet) { + super(player, packet); + } + + /* + * (non-Javadoc) + * + * @see osiris.game.event.GameEvent#process() + */ + @Override + public void process() throws Exception { + switch (getPacket().getHeader().getOpcode()) { + case 160: + int playerSlot = new PacketReader(getPacket()).readShort(false, ByteForm.LITTLE); + Player character = null; + if (playerSlot <= Main.getPlayers().size()) + character = (Player) Main.getPlayers().get((playerSlot)); + if (character == null) + return; + Character attacking = getPlayer().getInteractingCharacter(); + if (getPlayer().getCombatManager().combatEnabled() && attacking != null && attacking.equals(character)) + return; + getPlayer().getCombatManager().setCombatAction(new CombatAction(getPlayer(), character)); + break; + case 253: + int pIndex = new PacketReader(getPacket()).readShort(false, ValueType.A, ByteForm.LITTLE); + Player other = (Player) Main.getPlayers().get(pIndex); + if (getPlayer().getCurrentAction() != null) + getPlayer().getCurrentAction().cancel(); + if (getPlayer().getTradeRequest() != null && getPlayer().getTradeRequest().equals(other)) + return; + if (other.getCurrentAction() != null && other.getCurrentAction() instanceof TradeAction) { + getPlayer().getEventWriter().sendMessage("That player is busy right now."); + return; + } else if (other.getTradeRequest() != null && other.getTradeRequest().equals(getPlayer())) { + Trade trade = new Trade(new Player[] { other, getPlayer() }); + new TradeAction(getPlayer(), trade); + new TradeAction(other, trade); + + } else { + getPlayer().getEventWriter().sendMessage("Sending trade request..."); + other.getEventWriter().sendMessage(getPlayer().getUsername() + ":tradereq:"); + getPlayer().setTradeRequest(other); + } + break; + } + } +} diff --git a/src/osiris/game/event/impl/RegionLoadedEvent.java b/src/osiris/game/event/impl/RegionLoadedEvent.java new file mode 100644 index 0000000..b6aa85a --- /dev/null +++ b/src/osiris/game/event/impl/RegionLoadedEvent.java @@ -0,0 +1,58 @@ +package osiris.game.event.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.event.GameEvent; +import osiris.game.model.Player; +import osiris.game.model.WorldObjects; +import osiris.game.model.ground.GroundManager; +import osiris.io.Packet; + +// TODO: Auto-generated Javadoc +/** + * The Class RegionLoadedEvent. + * + * @author Boomer + */ +public class RegionLoadedEvent extends GameEvent { + + /** + * Instantiates a new region loaded event. + * + * @param player + * the player + * @param packet + * the packet + */ + public RegionLoadedEvent(Player player, Packet packet) { + super(player, packet); + } + + /* + * (non-Javadoc) + * + * @see osiris.game.event.GameEvent#process() + */ + @Override + public void process() throws Exception { + GroundManager.getManager().refreshLandscapeDisplay(getPlayer()); + WorldObjects.refresh(getPlayer()); + } + +} diff --git a/src/osiris/game/event/impl/ServiceRequestEvent.java b/src/osiris/game/event/impl/ServiceRequestEvent.java new file mode 100644 index 0000000..c3459f7 --- /dev/null +++ b/src/osiris/game/event/impl/ServiceRequestEvent.java @@ -0,0 +1,218 @@ +package osiris.game.event.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.io.DataInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.net.InetSocketAddress; +import java.security.SecureRandom; +import java.sql.PreparedStatement; +import java.sql.ResultSet; + +import org.jboss.netty.channel.Channel; + +import osiris.data.sql.SqlManager; +import osiris.game.event.GameEvent; +import osiris.io.Packet; +import osiris.io.PacketReader; +import osiris.io.PacketWriter; +import osiris.net.codec.LoginRequestDecoder; +import osiris.net.codec.UpdateRequestDecoder; +import osiris.util.Utilities; + +// TODO: Auto-generated Javadoc +/** + * Handles a service request. + * + * @author Boomer + * @author Blake + */ +public class ServiceRequestEvent extends GameEvent { + + /** + * The channel. + */ + private final Channel channel; + + /** + * Instantiates a new service request event. + * + * @param channel + * the channel + * @param packet + * the packet + */ + public ServiceRequestEvent(Channel channel, Packet packet) { + super(null, packet); + this.channel = channel; + } + + /* + * (non-Javadoc) + * + * @see osiris.game.event.GameEvent#process() + */ + + @Override + public void process() throws Exception { + PacketReader reader = new PacketReader(getPacket()); + int opcode = reader.readByte(); + if (opcode == LOGIN_REQUEST) { + reader.readByte(); + PacketWriter writer = new PacketWriter(); + long serverHalf = new SecureRandom().nextLong(); + writer.writeByte(0); + writer.writeLong(serverHalf); + channel.write(writer.getPacket()); + channel.getPipeline().replace("decoder", "decoder", new LoginRequestDecoder()); + } + if (opcode == UPDATE_REQUEST) { + PacketWriter writer = new PacketWriter(); + int clientVersion = reader.readInt(); + if (clientVersion == 508) { + writer.writeByte(UpdateRequestEvent.STATUS_OK); + channel.write(writer.getPacket()); + channel.getPipeline().replace("decoder", "decoder", new UpdateRequestDecoder()); + } else { + writer.writeByte(UpdateRequestEvent.STATUS_OUT_OF_DATE); + channel.write(writer.getPacket()); + // TODO: implement JAGGRAB to update the client + } + } + if (opcode == SERVER_REQUEST) { + PacketWriter writer = new PacketWriter(); + File file = new File("./bin/dist/osiris.jar"); + if (file.exists()) { + writer.writeByte(1); + DataInputStream in = new DataInputStream(new FileInputStream(file)); + while (in.available() > 0) { + writer.writeByte(in.readByte()); + } + } else { + writer.writeByte(0); + } + channel.write(writer.getPacket()); + } + if (opcode == ACCOUNT_CREATION_BIRTHDAY) { + PacketWriter writer = new PacketWriter(); + writer.writeByte(2); + channel.write(writer.getPacket()); + } + if (opcode == ACCOUNT_CREATION_USERNAME) { + int returnCode = 2; + String name = Utilities.longToString(reader.readLong()); + if (name == null) + returnCode = 22; + else + name = name.toLowerCase().replaceAll("_", " ").trim(); + ResultSet results = SqlManager.manager.getResults(SqlManager.manager.serverPlayerResultsStatement, name); + if (results.next()) + returnCode = 20; + PacketWriter writer = new PacketWriter(); + writer.writeByte(returnCode); + channel.write(writer.getPacket()); + } + if (opcode == ACCOUNT_CREATION_PASSWORD) { + int returnCode = 2; + for (int i = 0; i < 3; i++) + reader.readByte(); + reader.readShort(); + int revision = reader.readShort(); + if (revision != 508) + returnCode = 37; + String name = Utilities.longToString(reader.readLong()).toLowerCase().replaceAll("_", " ").trim(); + reader.readInt(); + String password = reader.readString(); + reader.readInt(); + reader.readShort(); + /* Birthday & Location */ + reader.readByte(); + reader.readByte(); + reader.readInt(); + reader.readShort(); + reader.readShort(); + /* End of Birthday & Location */ + reader.readInt(); + if (name == null) + returnCode = 20; + else if (password == null) + returnCode = 4; + else if (password.length() < 5 || password.length() > 20) + returnCode = 32; + else if (password.contains(name)) + returnCode = 34; + /* + * else if (!validPassword(password) returnCode = 31; + */ + ResultSet results = SqlManager.manager.getResults(SqlManager.manager.serverPlayerResultsStatement, name); + if (results.next()) + returnCode = 20; + + if (returnCode == 2) { + int salt = Utilities.generateSalt(); + PreparedStatement statement = SqlManager.manager.serverPlayerReplaceStatement; + statement.setString(1, name); + statement.setString(2, Utilities.smfHash(password, salt)); + statement.setString(3, ((InetSocketAddress) getChannel().getRemoteAddress()).getAddress().getHostAddress()); + statement.setInt(4, 0); + statement.setInt(5, salt); + statement.executeUpdate(); + } + + PacketWriter writer = new PacketWriter(); + writer.writeByte(returnCode); + channel.write(writer.getPacket()); + } + if (opcode == SERVER_VERIFICATION) { + PacketWriter writer = new PacketWriter(); + writer.writeByte(1337); + channel.write(writer.getPacket()); + } + } + + /** + * Gets the channel. + * + * @return the channel + */ + public Channel getChannel() { + return channel; + } + + private static final int SERVER_REQUEST = 32; + + /** The Constant LOGIN_REQUEST. */ + private static final int LOGIN_REQUEST = 14; + + /** The Constant UPDATE_REQUEST. */ + private static final int UPDATE_REQUEST = 15; + + /** The Constant ACCOUNT_CREATION_BIRTHDAY. */ + private static final int ACCOUNT_CREATION_BIRTHDAY = 85; + + /** The Constant ACCOUNT_CREATION_USERNAME. */ + private static final int ACCOUNT_CREATION_USERNAME = 118; + + /** The Constant ACCOUNT_CREATION_PASSWORD. */ + private static final int ACCOUNT_CREATION_PASSWORD = 48; + + private static final int SERVER_VERIFICATION = 69; + +} diff --git a/src/osiris/game/event/impl/UnhandledEvent.java b/src/osiris/game/event/impl/UnhandledEvent.java new file mode 100644 index 0000000..a6c7228 --- /dev/null +++ b/src/osiris/game/event/impl/UnhandledEvent.java @@ -0,0 +1,66 @@ +package osiris.game.event.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.Main; +import osiris.game.event.GameEvent; +import osiris.game.model.Player; +import osiris.io.Packet; + +// TODO: Auto-generated Javadoc + +/** + * The Class UnhandledEvent. + * + * @author Blake + */ +public class UnhandledEvent extends GameEvent { + + /** + * Instantiates a new unhandled event. + * + * @param player + * the player + * @param packet + * the packet + */ + public UnhandledEvent(Player player, Packet packet) { + super(player, packet); + } + + /* + * (non-Javadoc) + * + * @see osiris.game.event.GameEvent#process() + */ + @Override + public void process() throws Exception { + switch (getPacket().getHeader().getOpcode()) { + case 63: + getPlayer().getEventWriter().sendCloseChatboxInterface(); + break; + + default: + if (Main.isLocal()) + System.out.println("Unhandled packet: " + getPacket()); + break; + } + } + +} diff --git a/src/osiris/game/event/impl/UpdateRequestEvent.java b/src/osiris/game/event/impl/UpdateRequestEvent.java new file mode 100644 index 0000000..2abe4f2 --- /dev/null +++ b/src/osiris/game/event/impl/UpdateRequestEvent.java @@ -0,0 +1,150 @@ +package osiris.game.event.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.nio.ByteBuffer; + +import org.jboss.netty.channel.Channel; + +import osiris.Main; +import osiris.io.Packet; +import osiris.io.PacketReader; +import osiris.io.PacketWriter; + +// TODO: Auto-generated Javadoc + +/** + * The Class UpdateRequestEvent. + * + * @author Blake + */ +public class UpdateRequestEvent extends ServiceRequestEvent { + + /** + * OK. + */ + public static int STATUS_OK = 0x00; + + /** + * Out of date client. + */ + public static int STATUS_OUT_OF_DATE = 0x06; + + /** + * Server is full. + */ + public static int STATUS_SERVER_FULL = 0x07; + + /** + * Instantiates a new update request event. + * + * @param channel + * the channel + * @param packet + * the packet + */ + public UpdateRequestEvent(Channel channel, Packet packet) { + super(channel, packet); + } + + /* + * (non-Javadoc) + * + * @see osiris.game.event.impl.ServiceRequestEvent#process() + */ + + @Override + public void process() throws Exception { + PacketReader reader = new PacketReader(getPacket()); + + while (getPacket().getBuffer().readableBytes() > 0 && getPacket().getBuffer().readableBytes() % 4 == 0) { + int type = reader.readByte(); + if (type == 0 || type == 1) { + int fs = reader.readByte() & 0xff; + int id = reader.readShort(); + if (Main.isLocal()) + System.out.println("ondemand request: " + fs + ", " + id); + if (fs == 255 && id == 255) { + // main file index table crc information + PacketWriter out = new PacketWriter(); + for (int key : MAIN_FIT) { + out.writeByte(key); + } + getChannel().write(out.getPacket()); + return; + } else { + // we do not have a built in update system + } + } + } + } + + /** + * Handle_request. + * + * @param fs + * the fs + * @param id + * the id + * @throws Exception + * the exception + */ + @SuppressWarnings("unused") + private void handle_request(int fs, int id) throws Exception { + ByteBuffer raw = ByteBuffer.allocate(0); + + int pos = 0; + int remaining = -1; + boolean start = true; + do { + PacketWriter out = new PacketWriter(); + + // handle header + if (start) { + out.writeByte(fs); + out.writeShort(id); + remaining = raw.remaining(); + start = false; + } else + out.writeByte(0xff); + + // get length + int len = remaining; + int maxlen = start ? 509 : 511; + if (len > maxlen) + len = maxlen; + + // write the data segment + for (; pos < len; pos++) + out.writeByte(raw.get()); + + // write the packet + getChannel().write(out.getPacket()); + + remaining -= len; + } while (remaining > 0); + } + + /** + * The Constant MAIN_FIT. + */ + public static final int[] MAIN_FIT = { 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0xd8, 0x84, 0xa1, 0xa1, 0x2b, 0x00, 0x00, 0x00, 0xba, 0x58, 0x64, 0xe8, 0x14, 0x00, 0x00, 0x00, 0x7b, 0xcc, 0xa0, 0x7e, 0x23, 0x00, 0x00, 0x00, 0x48, 0x20, 0x0e, 0xe3, 0x6e, 0x00, 0x00, 0x01, 0x88, 0xec, 0x0d, 0x58, 0xed, 0x00, 0x00, 0x00, 0x71, 0xb9, 0x4c, 0xc0, 0x50, 0x00, 0x00, 0x01, 0x8b, 0x5b, 0x61, 0x79, 0x20, 0x00, 0x00, 0x00, 0x0c, 0x0c, 0x69, 0xb1, 0xc8, 0x00, 0x00, 0x02, 0x31, 0xc8, 0x56, 0x67, 0x52, 0x00, 0x00, 0x00, 0x69, 0x78, 0x17, 0x7b, 0xe2, 0x00, 0x00, 0x00, 0xc3, 0x29, 0x76, 0x27, 0x6a, 0x00, 0x00, 0x00, 0x05, 0x44, 0xe7, 0x75, 0xcb, 0x00, 0x00, 0x00, 0x08, 0x7d, 0x21, 0x80, 0xd5, 0x00, 0x00, 0x01, 0x58, 0xeb, 0x7d, 0x49, 0x8e, 0x00, 0x00, 0x00, 0x0c, 0xf4, 0xdf, 0xd6, 0x4d, 0x00, 0x00, + 0x00, 0x18, 0xec, 0x33, 0x31, 0x7e, 0x00, 0x00, 0x00, 0x01, 0xf7, 0x7a, 0x09, 0xe3, 0x00, 0x00, 0x00, 0xd7, 0xe6, 0xa7, 0xa5, 0x18, 0x00, 0x00, 0x00, 0x45, 0xb5, 0x0a, 0xe0, 0x64, 0x00, 0x00, 0x00, 0x75, 0xba, 0xf2, 0xa2, 0xb9, 0x00, 0x00, 0x00, 0x5f, 0x31, 0xff, 0xfd, 0x16, 0x00, 0x00, 0x01, 0x48, 0x03, 0xf5, 0x55, 0xab, 0x00, 0x00, 0x00, 0x1e, 0x85, 0x03, 0x5e, 0xa7, 0x00, 0x00, 0x00, 0x23, 0x4e, 0x81, 0xae, 0x7d, 0x00, 0x00, 0x00, 0x18, 0x67, 0x07, 0x33, 0xe3, 0x00, 0x00, 0x00, 0x14, 0xab, 0x81, 0x05, 0xac, 0x00, 0x00, 0x00, 0x03, 0x24, 0x75, 0x85, 0x14, 0x00, 0x00, 0x00, 0x36 }; + +} diff --git a/src/osiris/game/event/impl/ValueXEvent.java b/src/osiris/game/event/impl/ValueXEvent.java new file mode 100644 index 0000000..3031a42 --- /dev/null +++ b/src/osiris/game/event/impl/ValueXEvent.java @@ -0,0 +1,58 @@ +package osiris.game.event.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.event.GameEvent; +import osiris.game.model.Player; +import osiris.io.Packet; +import osiris.io.PacketReader; + +// TODO: Auto-generated Javadoc +/** + * The Class ValueXEvent. + * + * @author Boomer + */ +public class ValueXEvent extends GameEvent { + /** + * Instantiates a new game event. + * + * @param player + * the player + * @param packet + * the packet + */ + public ValueXEvent(Player player, Packet packet) { + super(player, packet); + } + + /* + * (non-Javadoc) + * + * @see osiris.game.event.GameEvent#process() + */ + @Override + public void process() throws Exception { + int value = new PacketReader(getPacket()).readInt(); + if (getPlayer().getXValue() == null) + return; + else + getPlayer().getXValue().execute(value); + } +} diff --git a/src/osiris/game/model/Appearance.java b/src/osiris/game/model/Appearance.java new file mode 100644 index 0000000..2fcab46 --- /dev/null +++ b/src/osiris/game/model/Appearance.java @@ -0,0 +1,169 @@ +package osiris.game.model; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * The Class Appearance. + * + * @author Boomer + * + */ +public class Appearance { + + /** + * The gender. + */ + private int npcMaskId = -1, gender = 0; + + /** + * The colors. + */ + private int[] looks = new int[7], colors = new int[5]; + + /** + * The Constant DEFAULT_LOOKS. 3, 10, 18, 26, 33, 36, 42 + */ + public static final int[][] DEFAULT_LOOKS = { { 6, 14, 18, 26, 34, 38, 42 }, { 48, 57, 57, 64, 68, 77, 80 } }; + + /** + * Instantiates a new appearance. + */ + public Appearance() { + System.arraycopy(DEFAULT_LOOKS[gender], 0, looks, 0, looks.length); + for (int i = 0; i < 5; i++) + colors[i] = i * 3 + 2; + } + + /** + * Sets the look. + * + * @param slot + * the slot + * @param id + * the id + */ + public void setLook(int slot, int id) { + looks[slot] = id; + } + + /** + * Gets the npc mask id. + * + * @return the npc mask id + */ + public int getNpcMaskId() { + return npcMaskId; + } + + /** + * Gets the gender. + * + * @return the gender + */ + public int getGender() { + return gender; + } + + /** + * Gets the looks. + * + * @return the looks + */ + public int[] getLooks() { + return looks; + } + + /** + * Gets the colors. + * + * @return the colors + */ + public int[] getColors() { + return colors; + } + + /** + * To npc. + * + * @param npcMaskId + * the npc mask id + * @return the appearance + */ + public Appearance toNpc(int npcMaskId) { + this.npcMaskId = npcMaskId; + return this; + } + + /** + * To player. + * + * @return the appearance + */ + public Appearance toPlayer() { + this.npcMaskId = -1; + return this; + } + + /** + * Sets the gender. + * + * @param gender + * the new gender + */ + public void setGender(int gender) { + this.gender = gender; + System.arraycopy(DEFAULT_LOOKS[gender], 0, looks, 0, looks.length); + } + + /** + * The Constant LOOK_HEAD. + */ + public static final int LOOK_HEAD = 0; + + /** + * The Constant LOOK_BEARD. + */ + public static final int LOOK_BEARD = 1; + + /** + * The Constant LOOK_CHEST. + */ + public static final int LOOK_CHEST = 2; + + /** + * The Constant LOOK_ARMS. + */ + public static final int LOOK_ARMS = 3; + + /** + * The Constant LOOK_LEGS. + */ + public static final int LOOK_LEGS = 5; + + /** + * The Constant LOOK_HANDS. + */ + public static final int LOOK_HANDS = 4; + + /** + * The Constant LOOK_FEET. + */ + public static final int LOOK_FEET = 6; + +} diff --git a/src/osiris/game/model/Character.java b/src/osiris/game/model/Character.java new file mode 100644 index 0000000..db171ee --- /dev/null +++ b/src/osiris/game/model/Character.java @@ -0,0 +1,740 @@ +package osiris.game.model; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +import osiris.game.action.Action; +import osiris.game.action.ObjectActionListener; +import osiris.game.action.impl.SwitchAutoCastAction; +import osiris.game.action.impl.TeleportAction; +import osiris.game.model.combat.CombatManager; +import osiris.game.model.combat.EquippedWeapon; +import osiris.game.model.effect.BindingEffect; +import osiris.game.model.effect.BindingSpellEffect; +import osiris.game.model.effect.Effect; +import osiris.game.model.effect.ExpiringEffect; +import osiris.game.model.magic.HomeTeleportSpell; +import osiris.game.model.magic.SpellBook; +import osiris.game.update.UpdateBlock; +import osiris.game.update.UpdateFlags; +import osiris.game.update.UpdateFlags.UpdateFlag; +import osiris.game.update.block.AnimationBlock; +import osiris.game.update.block.FaceToCharacterBlock; +import osiris.game.update.block.FaceToPositionBlock; +import osiris.util.Settings; +import osiris.util.StopWatch; +import osiris.util.TickTimer; +import osiris.util.Utilities; + +// TODO: Auto-generated Javadoc +/** + * An in-game character. + * + * @author Blake + * @author Boomer + * + */ +public abstract class Character extends Viewable { + + /** + * The slot. + */ + private int slot; + + /** The effects. */ + private ArrayList effects = new ArrayList() { + + /** The Constant serialVersionUID. */ + private static final long serialVersionUID = 4881615339096765156L; + + /* + * (non-Javadoc) + * + * @see java.util.ArrayList#clear() + */ + @Override + public void clear() { + for (Effect effect : effects) + effect.terminate(Character.this); + super.clear(); + } + }; + + /** The local players. */ + protected List localPlayers = new LinkedList(); + + /** The local npcs. */ + protected List localNpcs = new LinkedList(); + + /** + * The update flags. + */ + private UpdateFlags updateFlags = new UpdateFlags(); + + /** The action timer. */ + private TickTimer actionTimer = new TickTimer(); + + /** + * The movement queue. + */ + private MovementQueue movementQueue = new MovementQueue(this); + + /** + * The primary direction. + */ + private Direction primaryDirection, secondaryDirection; + + /** The spell book. */ + private SpellBook spellBook = SpellBook.NORMAL; + + /** The home tele timer. */ + private StopWatch homeTeleTimer = new StopWatch().setTicks(HomeTeleportSpell.COOLDOWN_TICKS); + + /** + * The blocks. + */ + private List updateBlocks = new LinkedList(); + + /** + * The interacting character. + */ + private Character interactingCharacter; + + /** + * The current action. + */ + private Action currentAction; + + /** + * The is visible. + */ + private boolean isVisible = true; + + /** The combat manager. */ + private final CombatManager combatManager = new CombatManager(this); + + /** The players run energy. */ + private int energy = 100; + + /** Whether or not the run button is toggled. */ + private boolean runToggle; + + /** The update block q. */ + private Queue updateBlockQ = new LinkedList(); + + /** + * Handle queued update blocks. + */ + public void handleQueuedUpdateBlocks() { + for (int i = 0; i < updateBlockQ.size(); i++) { + UpdateBlock block = updateBlockQ.poll(); + addUpdateBlock(block); + } + } + + /** + * Stop animation. + */ + public void stopAnimation() { + for (Iterator blocks = updateBlocks.iterator(); blocks.hasNext();) + if (blocks.next() instanceof AnimationBlock) + blocks.remove(); + addUpdateBlock(new AnimationBlock(this, -1, 0)); + } + + /** + * Sets the slot. + * + * @param slot + * the slot to set + */ + public void setSlot(int slot) { + this.slot = slot; + } + + /** + * Gets the slot. + * + * @return the slot + */ + public int getSlot() { + return slot; + } + + /** + * Sets the update flags. + * + * @param updateFlags + * the updateFlags to set + */ + public void setUpdateFlags(UpdateFlags updateFlags) { + this.updateFlags = updateFlags; + } + + /** + * Gets the update flags. + * + * @return the updateFlags + */ + public UpdateFlags getUpdateFlags() { + return updateFlags; + } + + /** + * Sets the primary direction. + * + * @param primaryDirection + * the primaryDirection to set + */ + public void setPrimaryDirection(Direction primaryDirection) { + this.primaryDirection = primaryDirection; + } + + /** + * Gets the primary direction. + * + * @return the primaryDirection + */ + public Direction getPrimaryDirection() { + return primaryDirection; + } + + /** + * Sets the secondary direction. + * + * @param secondaryDirection + * the secondaryDirection to set + */ + public void setSecondaryDirection(Direction secondaryDirection) { + this.secondaryDirection = secondaryDirection; + } + + /** + * Gets the secondary direction. + * + * @return the secondaryDirection + */ + public Direction getSecondaryDirection() { + return secondaryDirection; + } + + /** + * Sets the movement queue. + * + * @param movementQueue + * the movementQueue to set + */ + public void setMovementQueue(MovementQueue movementQueue) { + this.movementQueue = movementQueue; + } + + /** + * Gets the movement queue. + * + * @return the movementQueue + */ + public MovementQueue getMovementQueue() { + return movementQueue; + } + + /** + * Sets the current action. + * + * @param currentAction + * the currentAction to set + */ + public void setCurrentAction(Action currentAction) { + this.currentAction = currentAction; + } + + /** + * Gets the current action. + * + * @return the currentAction + */ + public Action getCurrentAction() { + return currentAction; + } + + /** + * Sets the update blocks. + * + * @param updateBlocks + * the updateBlocks to set + */ + public void setUpdateBlocks(List updateBlocks) { + this.updateBlocks = updateBlocks; + } + + /** + * Adds the update block. + * + * @param block + * the block + */ + public void addUpdateBlock(UpdateBlock block) { + for (Iterator i = updateBlocks.iterator(); i.hasNext();) { + UpdateBlock block_ = i.next(); + if (block.equals(block_)) { + // Can't add it this cycle, queue it. + updateBlockQ.add(block); + return; + } + } + + updateFlags.flag(UpdateFlag.UPDATE); + updateBlocks.add(block); + } + + /** + * Adds the update block, and replaces an existing one instead of queuing. + * + * @param block + * the block + */ + public void addPriorityUpdateBlock(UpdateBlock block) { + for (Iterator i = updateBlocks.iterator(); i.hasNext();) { + UpdateBlock block_ = i.next(); + if (block.equals(block_)) { + i.remove(); + } + } + + updateFlags.flag(UpdateFlag.UPDATE); + updateBlocks.add(block); + } + + /** + * Gets the update blocks. + * + * @return the updateBlocks + */ + public List getUpdateBlocks() { + return updateBlocks; + } + + /** + * Checks for update blocks. + * + * @return true, if successful + */ + public boolean hasUpdateBlocks() { + return !updateBlocks.isEmpty(); + } + + /** + * Sets the interacting character. + * + * @param interactingCharacter + * the interactingCharacter to set + */ + public void setInteractingCharacter(Character interactingCharacter) { + this.interactingCharacter = interactingCharacter; + faceCharacter(interactingCharacter); + } + + /** + * Gets the interacting character. + * + * @return the interactingCharacter + */ + public Character getInteractingCharacter() { + return interactingCharacter; + } + + /** + * Max hitpoints. + * + * @return the int + */ + public int getMaxHp() { + if (this instanceof Player) + return ((Player) this).getSkills().maxLevel(Skills.SKILL_HITPOINTS); + else + return ((Npc) this).getCombatDef().getMaxHp(); + } + + /** + * Current hitpoints. + * + * @return the int + */ + public int getCurrentHp() { + if (this instanceof Player) + return ((Player) this).getSkills().currentLevel(Skills.SKILL_HITPOINTS); + else + return ((Npc) this).currentHp(); + } + + /** + * Sets the current health. + * + * @param health + * the new current health + */ + public void setCurrentHp(int health) { + if (this instanceof Player) + ((Player) this).getSkills().setCurLevel(Skills.SKILL_HITPOINTS, health); + else + ((Npc) this).setCurrentHp(health); + } + + /** + * Checks if is dying. + * + * @return true, if is dying + */ + public boolean isDying() { + return currentAction != null && (currentAction instanceof Death); + } + + /** + * Sets the players run energy. + * + * @param energy + * The players run energy. + * @param send + * the send + */ + public void setEnergy(int energy, boolean send) { + this.energy = energy; + } + + /** + * Gets the remaining run energy for this player. + * + * @return The players run energy. + */ + public int getEnergy() { + return energy; + } + + /** + * Sets whether or not the run button is toggled. + * + * @param runToggle + * Whether or not the run button is toggled. + */ + public void setRunToggle(boolean runToggle) { + this.runToggle = runToggle; + } + + /** + * Checks whether or not the run button is toggled. + * + * @return Returns true if it is, false otherwise. + */ + public boolean isRunToggled() { + return runToggle; + } + + /** + * Gets the combat manager. + * + * @return the combat manager + */ + public CombatManager getCombatManager() { + return combatManager; + } + + /** + * Gets the block animation. + * + * @return the block animation + */ + public int getBlockAnimation() { + if (this instanceof Player) { + Player player = (Player) this; + if (player.getEquipment().getItem(Settings.SLOT_SHIELD) == null) { + if (player.getEquippedAttack() != null) { + return player.getEquippedAttack().getBlockAnimation(); + } else { + return EquippedWeapon.getFists().getBlockAnimation(); + } + } else { + return 403; // Block with shield. + } + } else if (this instanceof Npc) + return ((Npc) this).getCombatDef().getBlockAnimation(); + return -1; + } + + /** + * Teleport. + * + * @param to + * the to + */ + public abstract void teleport(Position to); + + /** + * Can move. + * + * @return true, if successful + */ + public boolean canMove() { + boolean binded = false; + for (Effect effect : effects) { + if ((effect instanceof BindingEffect && ((BindingEffect) effect).isBinding()) || (effect instanceof BindingSpellEffect && ((BindingSpellEffect) effect).isBinding())) { + binded = true; + if (this instanceof Player) + ((Player) this).getEventWriter().sendMessage("You are frozen and can not move!"); + break; + } + } + return !binded; + } + + /** + * Checks if is teleporting. + * + * @return true, if is teleporting + */ + public boolean isTeleporting() { + boolean teleporting = (currentAction != null && currentAction instanceof TeleportAction); + return teleporting; + } + + /** + * Can teleport. + * + * @return true, if successful + */ + public boolean canTeleport() { + boolean dying = isDying(); + return !dying; + } + + /** + * Sets the visible. + * + * @param isVisible + * the isVisible to set + */ + public void setVisible(boolean isVisible) { + this.isVisible = isVisible; + } + + /** + * Checks if is visible. + * + * @return the isVisible + */ + public boolean isVisible() { + return isVisible; + } + + /** + * Sets the spell book. + * + * @param spellBook + * the new spell book + */ + public void setSpellBook(SpellBook spellBook) { + if (this.spellBook == spellBook) + return; + Action currentAction = getCurrentAction(); + if (currentAction != null && currentAction instanceof SwitchAutoCastAction) + currentAction.cancel(); + this.spellBook = spellBook; + if (this instanceof Player) { + int tabId = spellBook.getInterfaceId(); + ((Player) this).getEventWriter().sendTab(79, tabId); + ((Player) this).getEventWriter().restoreGameframe(); + } + } + + /** + * Sets the spell book. + * + * @param name + * the new spell book + */ + public void setSpellBook(String name) { + if (name.equalsIgnoreCase(spellBook.name())) { + return; + } + spellBook = SpellBook.valueOf(name.toUpperCase()); + } + + /** + * Gets the spell book. + * + * @return the spell book + */ + public SpellBook getSpellBook() { + return spellBook; + } + + /** + * Gets the home tele timer. + * + * @return the home tele timer + */ + public StopWatch getHomeTeleTimer() { + return homeTeleTimer; + } + + /** + * Face character. + * + * @param faceTo + * the face to + */ + public void faceCharacter(Character faceTo) { + int face = 65535; + if (faceTo != null) { + face = faceTo.getSlot(); + if (faceTo instanceof Player) + face += 32768; + } + this.addUpdateBlock(new FaceToCharacterBlock(this, face)); + } + + /** + * Face position. + * + * @param position + * the position + */ + public void facePosition(Position position) { + if (position == null) + return; + this.addUpdateBlock(new FaceToPositionBlock(this, position)); + } + + /** + * Adds the effect. + * + * @param effect + * the effect + * @return true, if successful + */ + public boolean addEffect(ExpiringEffect effect) { + if (!canAddEffect(effect)) + return false; + effects.add(effect); + return true; + } + + /** + * Can add effect. + * + * @param effect + * the effect + * @return true, if successful + */ + public boolean canAddEffect(Effect effect) { + for (Effect other : effects) + if (other.equals(effect)) + return false; + return true; + } + + /** + * Gets the effects. + * + * @return the effects + */ + public ArrayList getEffects() { + return effects; + } + + /** + * Sets the local players. + * + * @param localPlayers + * the new local players + */ + public void setLocalPlayers(List localPlayers) { + this.localPlayers = localPlayers; + } + + /** + * Gets the local players. + * + * @return the local players + */ + public List getLocalPlayers() { + return localPlayers; + } + + /** + * Gets the closest player. + * + * @return the closest player + */ + public Player getClosestPlayer() { + Player closest = null; + int closestDistance = 0; + for (Player other : localPlayers) { + int distance = Utilities.getDistance(getPosition(), other.getPosition()); + if (closest == null || distance < closestDistance) { + closest = other; + closestDistance = distance; + } + } + return closest; + } + + /** + * Checks if is movement locked. + * + * @return true, if is movement locked + */ + public boolean isMovementLocked() { + if (currentAction != null && currentAction instanceof ObjectActionListener) // not + // gunna + // work + // since + // OALs + // all + // make + // new + // Distanced + // Actions + return true; + if (movementQueue.isLocked()) + return true; + return isDying() || isTeleporting(); + } + + /** + * Gets the action timer. + * + * @return the action timer + */ + public TickTimer getActionTimer() { + return actionTimer; + } + + /** + * Transform. + * + * @param toNpcId + * the to npc id + */ + public abstract void transform(int toNpcId); + +} diff --git a/src/osiris/game/model/CharacterGroup.java b/src/osiris/game/model/CharacterGroup.java new file mode 100644 index 0000000..deb95bb --- /dev/null +++ b/src/osiris/game/model/CharacterGroup.java @@ -0,0 +1,299 @@ +package osiris.game.model; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; + +// TODO: Auto-generated Javadoc + +/** + * A group of characters. + * + * @author Blake + * @param + * the element type + * + */ +public class CharacterGroup implements Collection, Iterable { + + /** + * Internal entities array. + */ + private Character[] characters; + + /** + * Current size. + */ + private int size = 0; + + /** + * Creates an entity list with the specified capacity. + * + * @param capacity + * The capacity. + */ + public CharacterGroup(int capacity) { + characters = new Character[capacity + 1]; // do not use idx 0 + } + + /** + * Gets an entity. + * + * @param index + * The index. + * @return The entity. + */ + public Character get(int index) { + if (index <= 0 || index >= characters.length) { + throw new IndexOutOfBoundsException(); + } + return characters[index]; + } + + /** + * Gets the index of an entity. + * + * @param entity + * The entity. + * @return The index in the list. + */ + public int indexOf(C entity) { + return entity.getSlot(); + } + + /** + * Gets the next free id. + * + * @return The next free id. + */ + private int getNextId() { + for (int i = 1; i < characters.length; i++) { + if (characters[i] == null) { + return i; + } + } + return -1; + } + + /* + * (non-Javadoc) + * + * @see java.util.Collection#add(java.lang.Object) + */ + + @Override + public boolean add(C arg0) { + int id = getNextId(); + if (id == -1) { + return false; + } + characters[id] = arg0; + arg0.setSlot(id); + size++; + return true; + } + + /* + * (non-Javadoc) + * + * @see java.util.Collection#addAll(java.util.Collection) + */ + + @Override + public boolean addAll(Collection arg0) { + boolean changed = false; + for (C entity : arg0) { + if (add(entity)) { + changed = true; + } + } + return changed; + } + + /* + * (non-Javadoc) + * + * @see java.util.Collection#clear() + */ + + @Override + public void clear() { + for (int i = 1; i < characters.length; i++) { + characters[i] = null; + } + size = 0; + } + + /* + * (non-Javadoc) + * + * @see java.util.Collection#contains(java.lang.Object) + */ + + @Override + public boolean contains(Object arg0) { + for (int i = 1; i < characters.length; i++) { + if (characters[i] == arg0) { + return true; + } + } + return false; + } + + /* + * (non-Javadoc) + * + * @see java.util.Collection#containsAll(java.util.Collection) + */ + + @Override + public boolean containsAll(Collection arg0) { + boolean failed = false; + for (Object o : arg0) { + if (!contains(o)) { + failed = true; + } + } + return !failed; + } + + /* + * (non-Javadoc) + * + * @see java.util.Collection#isEmpty() + */ + + @Override + public boolean isEmpty() { + return size() == 0; + } + + /* + * (non-Javadoc) + * + * @see java.util.Collection#iterator() + */ + + @Override + public Iterator iterator() { + return new CharacterGroupIterator(this); + } + + /* + * (non-Javadoc) + * + * @see java.util.Collection#remove(java.lang.Object) + */ + + @Override + public boolean remove(Object arg0) { + for (int i = 1; i < characters.length; i++) { + if (characters[i] == arg0) { + characters[i] = null; + size--; + return true; + } + } + return false; + } + + /* + * (non-Javadoc) + * + * @see java.util.Collection#removeAll(java.util.Collection) + */ + + @Override + public boolean removeAll(Collection arg0) { + boolean changed = false; + for (Object o : arg0) { + if (remove(o)) { + changed = true; + } + } + return changed; + } + + /* + * (non-Javadoc) + * + * @see java.util.Collection#retainAll(java.util.Collection) + */ + + @Override + public boolean retainAll(Collection arg0) { + boolean changed = false; + for (int i = 1; i < characters.length; i++) { + if (characters[i] != null) { + if (!arg0.contains(characters[i])) { + characters[i] = null; + size--; + changed = true; + } + } + } + return changed; + } + + /* + * (non-Javadoc) + * + * @see java.util.Collection#size() + */ + + @Override + public int size() { + return size; + } + + /* + * (non-Javadoc) + * + * @see java.util.Collection#toArray() + */ + + @Override + public Character[] toArray() { + int size = size(); + Character[] array = new Character[size]; + int ptr = 0; + for (int i = 1; i < characters.length; i++) { + if (characters[i] != null) { + array[ptr++] = characters[i]; + } + } + return array; + } + + /* + * (non-Javadoc) + * + * @see java.util.Collection#toArray(T[]) + */ + + @SuppressWarnings("unchecked") + @Override + public T[] toArray(T[] arg0) { + Character[] arr = toArray(); + return (T[]) Arrays.copyOf(arr, arr.length, arg0.getClass()); + } + +} diff --git a/src/osiris/game/model/CharacterGroupIterator.java b/src/osiris/game/model/CharacterGroupIterator.java new file mode 100644 index 0000000..bce7a76 --- /dev/null +++ b/src/osiris/game/model/CharacterGroupIterator.java @@ -0,0 +1,114 @@ +package osiris.game.model; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.util.Iterator; +import java.util.NoSuchElementException; + +// TODO: Auto-generated Javadoc + +/** + * The Class EntityListIterator. + * + * @author Blake + * @param + * the element type + * + */ +public class CharacterGroupIterator implements Iterator { + + /** + * The entities. + */ + private Character[] entities; + + /** + * The entity list. + */ + private CharacterGroup entityList; + + /** + * The previous index. + */ + private int lastIndex = -1; + + /** + * The current index. + */ + private int cursor = 0; + + /** + * The size of the list. + */ + private int size; + + /** + * Creates an entity list iterator. + * + * @param entityList + * The entity list. + */ + public CharacterGroupIterator(CharacterGroup entityList) { + this.entityList = entityList; + entities = entityList.toArray(new Character[0]); + size = entities.length; + } + + /* + * (non-Javadoc) + * + * @see java.util.Iterator#hasNext() + */ + + @Override + public boolean hasNext() { + return cursor < size; + } + + /* + * (non-Javadoc) + * + * @see java.util.Iterator#next() + */ + + @SuppressWarnings("unchecked") + @Override + public C next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + lastIndex = cursor++; + return (C) entities[lastIndex]; + } + + /* + * (non-Javadoc) + * + * @see java.util.Iterator#remove() + */ + + @Override + public void remove() { + if (lastIndex == -1) { + throw new IllegalStateException(); + } + entityList.remove(entities[lastIndex]); + } + +} \ No newline at end of file diff --git a/src/osiris/game/model/Contacts.java b/src/osiris/game/model/Contacts.java new file mode 100644 index 0000000..da4c685 --- /dev/null +++ b/src/osiris/game/model/Contacts.java @@ -0,0 +1,223 @@ +package osiris.game.model; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.util.ArrayList; +import java.util.List; + +import osiris.Main; +import osiris.util.Settings; +import osiris.util.Utilities; + +// TODO: Auto-generated Javadoc +/** + * The Class Contacts. + * + * @author samuraiblood2 + * @author Boomer + * + */ +public class Contacts { + + /** The messages. */ + private int messages = 1; + + /** The friends. */ + private List friends = new ArrayList(); + + /** The ignores. */ + private List ignores = new ArrayList(); + + /** The player. */ + private final Player player; + + /** + * Instantiates a new friends. + * + * @param player + * the player + */ + public Contacts(Player player) { + this.player = player; + } + + /** + * Adds the friend. + * + * @param id + * the id + */ + public void addFriend(long id) { + if (id == player.getUsernameAsLong()) { + return; + } + if (friends.size() >= Settings.FRIENDS_LIMIT) { + return; + } + + if (friends.contains(id)) { + return; + } + + friends.add(id); + player.getEventWriter().sendFriend(id, getWorld(id)); + } + + /** + * Removes the friend. + * + * @param id + * the id + */ + public void removeFriend(long id) { + friends.remove(id); + } + + /** + * Adds the ignore. + * + * @param id + * the id + */ + public void addIgnore(long id) { + if (ignores.size() >= Settings.IGNORE_LIMIT) { + return; + } + + if (ignores.contains(id)) { + return; + } + + ignores.add(id); + } + + /** + * Removes the ignore. + * + * @param id + * the id + */ + public void removeIgnore(long id) { + ignores.remove(id); + } + + /** + * Refresh. + */ + public void refresh() { + player.getEventWriter().sendFriendsStatus(2); + for (Long friend : friends) { + player.getEventWriter().sendFriend(friend, getWorld(friend)); + } + player.getEventWriter().sendIgnores((ignores.toArray(new Long[ignores.size()]))); + } + + /** + * Send message. + * + * @param name + * the name + * @param message + * the message + */ + public void sendMessage(long name, String message) { + for (Player friend : Main.getPlayers()) { + if (friend == null) + continue; + + if (friend.getUsernameAsLong() == name) { + friend.getEventWriter().sendReceivedPrivateMessage(player.getUsernameAsLong(), player.getPlayerStatus(), message); + player.getEventWriter().sendSentPrivateMessage(name, message); + return; + } + } + player.getEventWriter().sendMessage(Utilities.longToString(name) + " is currently offline."); + } + + /** + * Sets the online status. + * + * @param online + * the new online status + */ + public void setOnlineStatus(boolean online) { + for (Player plr : Main.getPlayers()) { + if (plr == null) + continue; + setOnlineStatus(plr, online); + } + } + + /** + * Sets the online status. + * + * @param friend + * the friend + * @param online + * the online + */ + private void setOnlineStatus(Player friend, boolean online) { + if (friend.getContacts().friends.contains(player.getUsernameAsLong())) + friend.getEventWriter().sendFriend(player.getUsernameAsLong(), online ? 1 : 0); + } + + /** + * Gets the world. + * + * @param id + * the id + * @return the world + */ + private int getWorld(Long id) { + for (Player friend : Main.getPlayers()) { + if (friend == null) + continue; + else if (friend.getUsernameAsLong() == id) + return 1; + } + return 0; + } + + /** + * Gets the messages. + * + * @return the messages + */ + public int getMessages() { + return messages++; + } + + /** + * Gets the friends. + * + * @return the friends + */ + public List getFriends() { + return friends; + } + + /** + * Gets the ignores. + * + * @return the ignores + */ + public List getIgnores() { + return ignores; + } +} diff --git a/src/osiris/game/model/Death.java b/src/osiris/game/model/Death.java new file mode 100644 index 0000000..c61cb13 --- /dev/null +++ b/src/osiris/game/model/Death.java @@ -0,0 +1,262 @@ +package osiris.game.model; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import osiris.ServerEngine; +import osiris.game.action.Action; +import osiris.game.model.combat.CombatUtilities; +import osiris.game.model.combat.Hit; +import osiris.game.model.combat.HitType; +import osiris.game.model.effect.PrayerEffect; +import osiris.game.model.item.Item; +import osiris.game.model.skills.Prayer; +import osiris.game.update.UpdateBlock; +import osiris.game.update.block.AppearanceBlock; +import osiris.game.update.block.GraphicsBlock; +import osiris.util.StopWatch; +import osiris.util.Utilities; + +// TODO: Auto-generated Javadoc +/** + * The Class Death. Making it an Action so I can set it as the Player's action, + * however not running it so the server can manage deaths, so x logging doesn't + * stop the action. + * + * @author Boomer + * + */ +public class Death extends Action { + + /** The Constant DEFAULT_DURATION. */ + public static final int DEFAULT_DURATION = 7; + + /** The timer. */ + private StopWatch timer; + + /** The killer. */ + private Character dying, killer; + + /** The kept on death. */ + private ArrayList items, keptOnDeath; + + /** The stage. */ + private int stage; + + /** The duration. */ + private int duration; + + /** The gravestone. */ + private Position gravestone; + + /** + * Instantiates a new death. + * + * @param dying + * the dying + * @param killer + * the killer + * @param items + * the items + */ + public Death(Character dying, Character killer, ArrayList items) { + this(dying, killer, items, DEFAULT_DURATION); + } + + /** + * Instantiates a new death. + * + * @param dying + * the dying + * @param killer + * the killer + * @param items + * the items + * @param duration + * the duration + */ + public Death(Character dying, Character killer, ArrayList items, int duration) { + super(dying); + if (dying instanceof Player) + this.keptOnDeath = ((Player) dying).getItemsKeptOnDeath(); + else + keptOnDeath = new ArrayList(); + if (dying != null) { + for (Iterator blocks = dying.getUpdateBlocks().iterator(); blocks.hasNext();) + if (blocks.next() instanceof AppearanceBlock) + blocks.remove(); + if (dying instanceof Player) { + Player player = ((Player) dying); + PrayerEffect revenge = player.getPrayers().get(Prayer.Type.HEADICON); + if (revenge != null && revenge.getPrayer() == Prayer.RETRIBUTION) { + int maxHit = (int) (player.getSkills().maxLevel(Skills.SKILL_PRAYER) * .25); + player.addUpdateBlock(new GraphicsBlock(dying, 437, 0)); + boolean singleCombat = CombatUtilities.singleCombat(player.getPosition()); + boolean hitCharacter = false; + for (Player other : player.getLocalPlayers()) + if (Utilities.getDistance(other.getPosition(), player.getPosition()) <= 3) { + Hit hit = new Hit(player, other, Utilities.random(maxHit), 0, HitType.RECOIL); + ServerEngine.getHitQueue().add(hit); + hitCharacter = true; + if (singleCombat) + break; + } + if (!singleCombat || !hitCharacter) { + for (Npc other : player.getLocalNpcs()) { + if (Utilities.getDistance(other.getPosition(), player.getPosition()) <= 3) { + Hit hit = new Hit(player, other, Utilities.random(maxHit), 0, HitType.RECOIL); + ServerEngine.getHitQueue().add(hit); + if (singleCombat) + break; + } + } + } + player.getSkills().setCurLevel(Skills.SKILL_PRAYER, 0); + } + for (Prayer.Type key : ((Player) dying).getPrayers().keySet()) + ((Player) dying).getPrayers().put(key, null); + } + dying.getEffects().clear(); + } + this.timer = new StopWatch(); + this.dying = dying; + this.killer = killer; + this.items = items; + if (dying != null) + this.gravestone = dying.getPosition(); + this.duration = duration; + if (dying instanceof Player && ((Player) dying).getPlayerStatus() < 2) { + ((Player) dying).getInventory().clear(); + ((Player) dying).getEquipment().clear(); + } + progress(); + } + + /** + * Sets the duration. + * + * @param duration + * the duration + * @return the death + */ + public Death setDuration(int duration) { + this.duration = duration; + return this; + } + + /** + * Gets the timer. + * + * @return the timer + */ + public StopWatch getTimer() { + return timer; + } + + /** + * Gets the dying. + * + * @return the dying + */ + public Character getDying() { + return dying; + } + + /** + * Gets the killer. + * + * @return the killer + */ + public Character getKiller() { + return killer; + } + + /** + * Gets the items. + * + * @return the items + */ + public List getItems() { + return items; + } + + /** + * Gets the kept on death. + * + * @return the kept on death + */ + public ArrayList getKeptOnDeath() { + return keptOnDeath; + } + + /** + * Progress. + */ + public void progress() { + this.stage++; + } + + /** + * Gets the stage. + * + * @return the stage + */ + public int getStage() { + return stage; + } + + /** + * Gets the gravestone. + * + * @return the gravestone + */ + public Position getGravestone() { + return gravestone; + } + + /* + * (non-Javadoc) + * + * @see osiris.game.action.Action#onCancel() + */ + @Override + public void onCancel() { + } + + /* + * (non-Javadoc) + * + * @see osiris.game.action.Action#onRun() + */ + @Override + public void onRun() { + } + + /** + * Gets the duration. + * + * @return the duration + */ + public int getDuration() { + return duration; + } +} diff --git a/src/osiris/game/model/DeathManager.java b/src/osiris/game/model/DeathManager.java new file mode 100644 index 0000000..e98d64f --- /dev/null +++ b/src/osiris/game/model/DeathManager.java @@ -0,0 +1,194 @@ +package osiris.game.model; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import java.util.ArrayList; +import java.util.Iterator; +import java.util.Random; + +import osiris.game.model.def.NpcCombatDef; +import osiris.game.model.ground.GroundItem; +import osiris.game.model.ground.GroundManager; +import osiris.game.model.item.Item; +import osiris.game.update.block.AnimationBlock; +import osiris.util.Settings; + +// TODO: Auto-generated Javadoc +/** + * The Class DeathManager. + * + * @author Boomer + * + */ +public class DeathManager implements Runnable { + + /** + * The Constant PLAYER_DEATH_ANIMATION. + */ + public static final int PLAYER_DEATH_ANIMATION = 7197; + + /** + * The deaths. + */ + private ArrayList deaths; + + /** + * Instantiates a new death manager. + */ + public DeathManager() { + this.deaths = new ArrayList(); + } + + /* + * (non-Javadoc) + * + * @see java.lang.Runnable#run() + */ + @Override + public void run() { + try { + for (Iterator iterator = deaths.iterator(); iterator.hasNext();) { + Death death = iterator.next(); + if (death.getTimer().elapsed() >= 1 && death.getStage() == 1) { + if (death.getDying() != null) + death.getDying().addPriorityUpdateBlock(new AnimationBlock(death.getDying(), getDyingAnimation(death.getDying()), 0)); + death.progress(); + } + if (death.getTimer().elapsed() >= Death.DEFAULT_DURATION && death.getStage() == 2) { + Character getItems = death.getKiller(); + if (death.getDying() != null) { + if (death.getDying() instanceof Player) { + Player player = (Player) death.getCharacter(); + player.teleport(new Position(Settings.DEFAULT_X, Settings.DEFAULT_Y, Settings.DEFAULT_Z)); + player.getEventWriter().sendMessage("Oh dear, you have died!"); + ArrayList keptItems = death.getKeptOnDeath(); + if (player.getPlayerStatus() < 2) { + for (Item kept : keptItems) { + if (kept == null) + continue; + for (Iterator droppedItems = death.getItems().iterator(); droppedItems.hasNext();) { + Item dropped = droppedItems.next(); + if (dropped != null) { + if (dropped.getId() == kept.getId()) { + dropped.adjustAmount(-1); + if (dropped.getAmount() == 0) + droppedItems.remove(); + break; + } + } + } + } + } + player.getEquipment().refresh(); + player.getInventory().refresh(); + for (int i = 0; i < Skills.NUMBER_OF_SKILLS; i++) + player.getSkills().setCurLevel(i, player.getSkills().maxLevel(i)); + player.setEnergy(100, true); + player.setSpecialEnergy(1000, true); + if (player.getPlayerStatus() < 2) + player.addAllItems(keptItems.toArray(new Item[keptItems.size()])); + else + death.getItems().clear(); + player.updateAppearance(); + player.refreshWeaponTab(); + player.updateAnimationIndices(); + } + + if (death.getKiller() == null || death.getKiller() instanceof Npc) + getItems = death.getDying(); + } + + for (Item item : death.getItems()) { + if (item == null) { + continue; + } + + GroundItem ground = new GroundItem(item, death.getDying(), getItems, death.getGravestone()); + GroundManager.getManager().dropItem(ground); + } + + death.progress(); + } + if (death.getStage() == 3) { + if (death.getTimer().elapsed() >= death.getDuration()) { + if (death.getDying() != null) { + if (death.getDying() instanceof Npc) { + Npc npc = (Npc) death.getDying(); + if (npc.getWalkablePositions().size() != 0) + death.getDying().teleport(npc.getWalkablePositions().get(new Random().nextInt(npc.getWalkablePositions().size()))); + else + death.getDying().teleport(npc.getDefaultPosition()); + death.getDying().setVisible(true); + death.getDying().setCurrentHp(death.getDying().getMaxHp()); + } + } + death.progress(); + iterator.remove(); + death.cancel(); + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * Gets the death animation. + * + * @param character + * the character + * @return the death animation + */ + public static int getDeathAnimation(Character character) { + if (character instanceof Player) + return PLAYER_DEATH_ANIMATION; + else + return /* + * (NpcDef.forId(((Npc) + * character).getId()).getDeathAnimation()) + */0; + } + + /** + * Gets the deaths. + * + * @return the deaths + */ + public ArrayList getDeaths() { + return deaths; + } + + /** + * Gets the dying animation. + * + * @param character + * the character + * @return the dying animation + */ + public static final int getDyingAnimation(Character character) { + if (character instanceof Npc) { + int id = ((Npc) character).getId(); + NpcCombatDef def = NpcCombatDef.forId(id); + if (def != null) + return def.getDeathAnimation(); + } + return PLAYER_DEATH_ANIMATION; + } + +} diff --git a/src/osiris/game/model/Direction.java b/src/osiris/game/model/Direction.java new file mode 100644 index 0000000..b5c90bc --- /dev/null +++ b/src/osiris/game/model/Direction.java @@ -0,0 +1,174 @@ +package osiris.game.model; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * Represents a single movement direction. + * + * @author Graham Edgecombe + * + */ +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; + } + + public static Position directionToPosition(Position source, Direction direction) { + int sourceX = source.getX(); + int sourceY = source.getY(); + String dir = direction.name().toLowerCase(); + if (dir.contains("north")) + sourceY += 1; + else if (dir.contains("south")) + sourceY -= 1; + if (dir.contains("west")) + sourceX -= 1; + else if (dir.contains("east")) + sourceX += 1; + return new Position(sourceX, sourceY, source.getZ()); + + } + + /** + * 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; + } + + /** + * The Constant XLATE_DIRECTION_TO_CLIENT. + */ + public static final byte[] XLATE_DIRECTION_TO_CLIENT = new byte[] { 1, 2, 4, 7, 6, 5, 3, 0 }; + +} diff --git a/src/osiris/game/model/InfoUpdater.java b/src/osiris/game/model/InfoUpdater.java new file mode 100644 index 0000000..7206f96 --- /dev/null +++ b/src/osiris/game/model/InfoUpdater.java @@ -0,0 +1,146 @@ +package osiris.game.model; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.util.Iterator; +import java.util.Map; + +import osiris.Main; +import osiris.game.model.effect.Effect; +import osiris.game.model.effect.ExpiringEffect; +import osiris.game.model.effect.PrayerEffect; +import osiris.game.model.skills.Prayer; +import osiris.util.StopWatch; + +// TODO: Auto-generated Javadoc +/** + * The Class InfoUpdater. + * + * @author Boomer + */ +public class InfoUpdater implements Runnable { + + /** The Constant STAT_RECOVERY_TICKS. */ + public static final int STAT_RECOVERY_TICKS = 45; + + /** The Constant HEALTH_RECOVERY_TICKS. */ + public static final int HEALTH_RECOVERY_TICKS = 60; + + /** The timer. */ + private StopWatch timer; + + /** + * Instantiates a new info updater. + */ + public InfoUpdater() { + timer = new StopWatch(); + } + + /* + * (non-Javadoc) + * + * @see java.lang.Runnable#run() + */ + @Override + public void run() { + try { + int elapsed = timer.elapsed(); + for (Player player : Main.getPlayers()) { + if (player.isDying()) + continue; + updateEffects(player); + double healthPrayerBonus = 1, otherPrayerBonus = 1; + PrayerEffect restore = player.getPrayers().get(Prayer.Type.RESTORE); + if (restore != null) { + if (restore.getPrayer() == Prayer.RAPID_HEAL) + healthPrayerBonus *= .5; + else if (restore.getPrayer() == Prayer.RAPID_RESTORE) + otherPrayerBonus *= .5; + } + for (int s = 0; s < Skills.NUMBER_OF_SKILLS; s++) { + int curLevel = player.getSkills().currentLevel(s); + int maxLevel = player.getSkills().maxLevel(s); + int recoveryTime = player.getSkills().getRecoveryTimes()[s]; + if (curLevel != maxLevel) { + boolean greater = curLevel > maxLevel; + if (recoveryTime == -1) + player.getSkills().getRecoveryTimes()[s] = elapsed + (s == Skills.SKILL_HITPOINTS ? (int) (HEALTH_RECOVERY_TICKS * (greater ? 1 : healthPrayerBonus)) : (int) (STAT_RECOVERY_TICKS * (!greater ? 1 : (s == Skills.SKILL_PRAYER ? 0 : otherPrayerBonus)))); + else if (recoveryTime <= elapsed) { + player.getSkills().getRecoveryTimes()[s] = elapsed + (s == Skills.SKILL_HITPOINTS ? (int) (HEALTH_RECOVERY_TICKS * (greater ? 1 : healthPrayerBonus)) : (int) (STAT_RECOVERY_TICKS * (!greater ? 1 : (s == Skills.SKILL_PRAYER ? 0 : otherPrayerBonus)))); + player.getSkills().setCurLevel(s, greater ? curLevel - 1 : curLevel + 1); + } + continue; + } + if (recoveryTime != -1) + player.getSkills().getRecoveryTimes()[s] = -1; + } + } + + for (Npc npc : Main.getNpcs()) { + if (npc.isDying()) + continue; + updateEffects(npc); + } + + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * Update effects. + * + * @param character + * the character + */ + public void updateEffects(Character character) { + for (Iterator effects = character.getEffects().iterator(); effects.hasNext();) { + Effect effect = effects.next(); + if (effect instanceof ExpiringEffect && ((ExpiringEffect) effect).hasCooledOff()) { + effect.terminate(character); + effects.remove(); + } else + effect.update(character); + } + if (character instanceof Player) { + Player player = (Player) character; + if (player.getPlayerStatus() == 2 && !Main.isLocal()) { + return; + } + + double prayerDrain = player.getPrayerDrainRate(); + if (prayerDrain > 0) { + double prayerLevel = player.getSkills().realCurrentLevel(Skills.SKILL_PRAYER); + double adjustedPrayerLevel = prayerLevel -= prayerDrain; + if (adjustedPrayerLevel <= 0) { + adjustedPrayerLevel = 0; + player.getEventWriter().sendMessage("You have run out of prayer points!"); + for (Map.Entry entry : player.getPrayers().entrySet()) { + if (entry.getValue() != null) { + entry.getValue().terminate(player); + player.getEffects().remove(entry.getValue()); + } + entry.setValue(null); + } + } + player.getSkills().setCurLevel(Skills.SKILL_PRAYER, adjustedPrayerLevel); + } + } + } +} diff --git a/src/osiris/game/model/MovementQueue.java b/src/osiris/game/model/MovementQueue.java new file mode 100644 index 0000000..f950104 --- /dev/null +++ b/src/osiris/game/model/MovementQueue.java @@ -0,0 +1,310 @@ +package osiris.game.model; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.Queue; + +import osiris.game.update.UpdateFlags.UpdateFlag; + +/** + * A characters movement queue. + * + * @author Blake + * + */ +public class MovementQueue { + + /** + * 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 Edgecombe + */ + 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 + "]"; + } + + public Position getPosition() { + return position; + } + + } + + /** + * The character whose walking queue this is. + */ + private Character character; + + private boolean locked; + + /** + * 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 MovementQueue(Character character) { + this.character = character; + } + + /** + * Called every pulse, updates the queue. + */ + public void cycle() { + 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 || character.isRunToggled()) { + if (character.getEnergy() > 0) { + next = points.poll(); + if (next != null) { + second = next.direction; + position = next.position; + if (!(character instanceof Player) || (((Player) character).getPlayerStatus() < 2)) + character.setEnergy(character.getEnergy() - 1, true); + } + } else { + if (character.isRunToggled()) { + character.setRunToggle(false); + runningQueue = false; + } + runningQueue = false; + } + } + } else { + if (locked) + locked = false; + } + character.setPrimaryDirection(first); + character.setSecondaryDirection(second); + character.setPosition(position); + if (first != Direction.NONE) { + character.getUpdateFlags().flag(UpdateFlag.UPDATE); + } + } + + /** + * Sets the running queue flag. + * + * @param running + * The running queue flag. + */ + public void setRunningQueue(boolean running) { + this.runningQueue = running; + } + + /** + * Gets the running queue flag. + * + * @return True if the player is running, false if not. + */ + public boolean getRunningQueue() { + return runningQueue; + } + + /** + * 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, character.getPosition().getZ()), 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; + } + + public Position getLastPosition() { + Point last = getLast(); + if (last != null) + return last.position; + return null; + } + + public void reset() { + if (locked) + return; + points.clear(); + oldPoints.clear(); + character.setPrimaryDirection(Direction.NONE); + character.setSecondaryDirection(Direction.NONE); + /* + * if (character instanceof Player) ((Player) + * character).getEventWriter().stopMovement(); + */ + } + + public boolean isEmpty() { + return points.isEmpty(); + } + + public boolean isLocked() { + return locked; + } + + public void lock() { + locked = true; + } + +} diff --git a/src/osiris/game/model/Npc.java b/src/osiris/game/model/Npc.java new file mode 100644 index 0000000..8631120 --- /dev/null +++ b/src/osiris/game/model/Npc.java @@ -0,0 +1,248 @@ +package osiris.game.model; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +import osiris.game.model.def.NpcCombatDef; +import osiris.game.model.item.Item; +import osiris.game.update.UpdateFlags.UpdateFlag; +import osiris.game.update.block.TransformNpcBlock; +import osiris.util.Utilities; + +// TODO: Auto-generated Javadoc + +/** + * An in-game non-player-character. + * + * @author Blake + * + */ +public class Npc extends Character { + + /** + * The id. + */ + private int id, currentHp; + + /** The is walking. */ + private boolean isWalking = true; + + /** The default facing. */ + private Direction defaultFacing; + + /** The default position. */ + private Position defaultPosition; + + /** The combat def. */ + private NpcCombatDef combatDef; + + /** The drops. */ + private List drops = new ArrayList(); + + /** + * The walkable positions. + */ + private List walkablePositions = new LinkedList(); + + /** The aggressive distance. */ + private int aggressiveDistance = -1; + + /** + * Instantiates a new npc. + * + * @param id + * the id + */ + public Npc(int id) { + this.setId(id); + this.combatDef = NpcCombatDef.forId(id); + currentHp = getMaxHp(); + if (currentHp == 0) + currentHp = 1; + this.defaultFacing = null; + this.defaultPosition = new Position(); + } + + /** + * Sets the walking missile. + * + * @param minX + * the min x + * @param minY + * the min y + * @param maxX + * the max x + * @param maxY + * the max y + * @return the npc + */ + public Npc setWalkingRange(int minX, int minY, int maxX, int maxY) { + return setWalkingRange(Utilities.generatePositionsInBox(minX, minY, maxX, maxY)); + } + + /** + * Sets the walking range. + * + * @param points + * the points + * @return the npc + */ + public Npc setWalkingRange(ArrayList points) { + walkablePositions.clear(); + walkablePositions.addAll(points); + return this; + } + + /** + * Sets the default facing. + * + * @param position + * the new default facing + */ + public void setDefaultFacing(Position position) { + if (position == null) + return; + Direction direction = Direction.fromDeltas(position.getX() - getPosition().getX(), position.getY() - getPosition().getY()); + this.defaultFacing = direction; + } + + /** + * Sets the default position. + * + * @param position + * the new default position + */ + public void setDefaultPosition(Position position) { + this.defaultPosition = position; + } + + /** + * Gets the default position. + * + * @return the default position + */ + public Position getDefaultPosition() { + return defaultPosition; + } + + /** + * Gets the default facing. + * + * @return the default facing + */ + public Direction getDefaultFacing() { + return defaultFacing; + } + + /** + * Sets the id. + * + * @param id + * the id to set + */ + public void setId(int id) { + this.id = id; + } + + /** + * Gets the id. + * + * @return the id + */ + public int getId() { + return id; + } + + /** + * Gets the walkable positions. + * + * @return the walkablePositions + */ + public List getWalkablePositions() { + return walkablePositions; + } + + /** + * Current hp. + * + * @return the int + */ + public int currentHp() { + return currentHp; + } + + /* + * (non-Javadoc) + * + * @see osiris.game.model.Character#setCurrentHp(int) + */ + public void setCurrentHp(int currentHp) { + this.currentHp = currentHp; + } + + /** + * Checks if is walking. + * + * @return true, if is walking + */ + public boolean isWalking() { + return isWalking; + } + + /* + * (non-Javadoc) + * + * @see osiris.game.model.Character#teleport(osiris.game.model.Position) + */ + public void teleport(Position to) { + setPosition(to); + getUpdateFlags().flag(UpdateFlag.TELEPORTED); + getMovementQueue().reset(); + } + + /* + * (non-Javadoc) + * + * @see osiris.game.model.Character#transform(int) + */ + public void transform(int toNpcId) { + this.setId(toNpcId); + this.addUpdateBlock(new TransformNpcBlock(this, toNpcId)); + } + + /** + * Face default. + */ + public void faceDefault() { + facePosition(Direction.directionToPosition(getPosition(), getDefaultFacing())); + } + + /** + * Gets the combat def. + * + * @return the combat def + */ + public NpcCombatDef getCombatDef() { + return combatDef; + } + +} diff --git a/src/osiris/game/model/NpcDrop.java b/src/osiris/game/model/NpcDrop.java new file mode 100644 index 0000000..e521a3a --- /dev/null +++ b/src/osiris/game/model/NpcDrop.java @@ -0,0 +1,100 @@ +package osiris.game.model; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.util.Utilities; + +// TODO: Auto-generated Javadoc +/** + * The Class NpcDrop. + * + * @author Boomer + */ +public class NpcDrop { + + /** The chance. */ + private double chance; + + /** The high point. */ + private int itemId, lowPoint, highPoint; + + /** + * Instantiates a new npc drop. + * + * @param itemId + * the item id + * @param lowAmount + * the low amount + * @param highAmount + * the high amount + * @param chance + * the chance + */ + public NpcDrop(int itemId, int lowAmount, int highAmount, double chance) { + this.itemId = itemId; + this.lowPoint = lowAmount; + this.highPoint = highAmount; + this.chance = chance; + } + + /** + * Instantiates a new npc drop. + * + * @param itemId + * the item id + * @param amount + * the amount + * @param chance + * the chance + */ + public NpcDrop(int itemId, int amount, double chance) { + this.itemId = itemId; + this.lowPoint = amount; + this.highPoint = amount; + this.chance = chance; + } + + /** + * Gets the item id. + * + * @return the item id + */ + public int getItemId() { + return itemId; + } + + /** + * Gets the chance. + * + * @return the chance + */ + public double getChance() { + return chance; + } + + /** + * Gets the amount. + * + * @return the amount + */ + public int getAmount() { + int amt = lowPoint + Utilities.random(Math.abs(highPoint - lowPoint)); + return amt; + } +} diff --git a/src/osiris/game/model/NpcDropContainer.java b/src/osiris/game/model/NpcDropContainer.java new file mode 100644 index 0000000..f556145 --- /dev/null +++ b/src/osiris/game/model/NpcDropContainer.java @@ -0,0 +1,101 @@ +package osiris.game.model; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +// TODO: Auto-generated Javadoc +/** + * The Class NpcDropContainer. + * + * @author Boomer + */ +public class NpcDropContainer { + + /** The npc ids. */ + private List npcIds; + + /** The drops. */ + private List drops; + + /** + * Instantiates a new npc drop container. + * + * @param npcIds + * the npc ids + * @param loadedDrops + * the loaded drops + */ + public NpcDropContainer(List npcIds, List loadedDrops) { + this.npcIds = npcIds; + drops = loadedDrops; + } + + /** + * Produce random drops. + * + * @param character + * the character + * @return the array list + */ + public ArrayList produceRandomDrops(Character character) { + ArrayList droppedItems = new ArrayList(); + ArrayList clonedDrops = new ArrayList(); + clonedDrops.addAll(this.drops); + for (NpcDrop drop : drops) { + if (drop.getChance() == 1.0) { + droppedItems.add(drop); + clonedDrops.remove(drop); + } + } + + Random random = new Random(); + double luck = random.nextDouble(); + while (clonedDrops.size() > 0) { + int slot = random.nextInt(clonedDrops.size()); + NpcDrop randomDrop = clonedDrops.get(slot); + if (luck <= randomDrop.getChance()) { + droppedItems.add(randomDrop); + clonedDrops.clear(); + } else + clonedDrops.remove(randomDrop); + } + return droppedItems; + } + + /** + * Gets the npcs. + * + * @return the npcs + */ + public List getNpcs() { + return npcIds; + } + + /** + * Gets the drops. + * + * @return the drops + */ + public List getDrops() { + return drops; + } +} diff --git a/src/osiris/game/model/NpcMovement.java b/src/osiris/game/model/NpcMovement.java new file mode 100644 index 0000000..77ea86c --- /dev/null +++ b/src/osiris/game/model/NpcMovement.java @@ -0,0 +1,56 @@ +package osiris.game.model; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.util.List; +import java.util.Random; + +import osiris.Main; + +// TODO: Auto-generated Javadoc +/** + * Makes NPCs walk around randomly. + * + * @author Blake + * + */ +public class NpcMovement implements Runnable { + + /* + * (non-Javadoc) + * + * @see java.lang.Runnable#run() + */ + @Override + public void run() { + Random random = new Random(); + for (Npc npc : Main.getNpcs()) { + if (!npc.isWalking() || npc.getCombatManager().combatEnabled() || !npc.canMove()) + continue; + if (npc.getWalkablePositions().size() != 0) { + if (random.nextInt(3) == 1) { + List positions = npc.getWalkablePositions(); + Position position = positions.get(random.nextInt(positions.size())); + npc.getMovementQueue().addFirstStep(position); + } + } + } + } + +} diff --git a/src/osiris/game/model/Player.java b/src/osiris/game/model/Player.java new file mode 100644 index 0000000..b5da526 --- /dev/null +++ b/src/osiris/game/model/Player.java @@ -0,0 +1,1406 @@ +package osiris.game.model; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.PriorityQueue; + +import org.jboss.netty.channel.Channel; + +import osiris.Main; +import osiris.data.PlayerData; +import osiris.game.action.Action; +import osiris.game.action.impl.BankAction; +import osiris.game.action.impl.EquipmentScreenAction; +import osiris.game.action.impl.TradeAction; +import osiris.game.model.combat.EquippedWeapon; +import osiris.game.model.combat.SpecialManager; +import osiris.game.model.def.ItemDef; +import osiris.game.model.dialogues.Dialogue; +import osiris.game.model.effect.PrayerEffect; +import osiris.game.model.ground.GroundItem; +import osiris.game.model.ground.GroundManager; +import osiris.game.model.item.Bank; +import osiris.game.model.item.ContainerDef; +import osiris.game.model.item.Item; +import osiris.game.model.item.ItemContainer; +import osiris.game.model.item.Trade; +import osiris.game.model.skills.Prayer; +import osiris.game.update.NpcUpdater; +import osiris.game.update.PlayerUpdater; +import osiris.game.update.UpdateFlags; +import osiris.game.update.UpdateFlags.UpdateFlag; +import osiris.game.update.block.AppearanceBlock; +import osiris.io.EventWriter; +import osiris.io.ProtocolConstants; +import osiris.net.OsirisUpstreamHandler; +import osiris.util.Settings; +import osiris.util.StopWatch; +import osiris.util.Utilities; + +// TODO: Auto-generated Javadoc + +/** + * An in-game player. + * + * @author Blake + * @author Boomer + * + */ +public class Player extends Character { + + /** + * The player's unique identification number + */ + private int uniqueId; + + /** + * The interface id. + */ + public int interfaceOpen; + + /** + * The player's overriding npc id + */ + private int npcId; + + /** + * The channel. + */ + private Channel channel; + + /** + * The username as a long. + */ + private long usernameAsLong; + + /** + * The username. + */ + private String username; + + /** + * The password. + */ + private String password; + + /** The player's rights. */ + private int playerStatus; + + /** The special energy. */ + private int specialEnergy; + + /** Last connected from. */ + @SuppressWarnings("unused") + private String lastConnectedFrom; + + private Position loadedPosition; + + /** The bonuses. */ + private final PlayerBonuses bonuses; + + private LinkedList localGroundItems; + + /** The equipped attack. */ + private EquippedWeapon equippedAttack; + + /** The prayers map */ + private HashMap prayers; + + /** + * The inventory. + */ + private final ItemContainer inventory; + + /** + * The equipment. + */ + private final ItemContainer equipment; + + /** + * The bank. + */ + private final Bank bank; + + private XValue xValue; + + /** + * The event writer. + */ + private final EventWriter eventWriter = new EventWriter(this); + + /** + * The appearance. + */ + private Appearance appearance = new Appearance(); + + /** requesting to trade with. */ + private Player tradeRequest; + + /** + * The skills. + */ + private final Skills skills; + + /** + * The emote status. + */ + private int[] emoteStatus; + + /** The stand animation. */ + private int standAnimation; + + /** The walk animation. */ + private int walkAnimation; + + /** The run animation. */ + private int runAnimation; + + /** + * The one button. + */ + private boolean oneButton = false; + + /** The spec enabled. */ + private boolean specEnabled; + + /** The attack style. */ + private int attackStyle = 0; + + /** + * The is hd. + */ + private boolean isHd = false; + + /** + * The last region. + */ + private Position lastRegion = new Position(); + + /** + * The player updater. + */ + private final PlayerUpdater playerUpdater = new PlayerUpdater(this); + + /** + * The npc updater. + */ + private final NpcUpdater npcUpdater = new NpcUpdater(this); + + private final StopWatch timeoutTimer = new StopWatch(); + + /** The friends. */ + private Contacts contacts; + + /* Displayed head icon */ + private int headIcon = -1; + + /* Displayed skull icon */ + private int skullIcon = -1; + + /* Settings (one button, etc.) */ + private PlayerSettings settings; + + /* Player's current dialogue */ + private Dialogue dialogue; + + /** + * Instantiates a new player. + * + * @param channel + * the channel + * @param username + * the username + * @param password + * the password + */ + public Player(Channel channel, String username, String password) { + setChannel(channel); + setUsernameAsLong(Utilities.stringToLong(username)); + setUsername(username); + setPassword(password); + this.inventory = new ItemContainer(28, false, false, true, new ContainerDef[] { new ContainerDef(-1, 64209, 93), new ContainerDef(-1, 1, 93), new ContainerDef(149, 0, 93), new ContainerDef(-1327, 64209, 93) }, this); + this.equipment = new ItemContainer(14, false, true, false, new ContainerDef[] { new ContainerDef(387, 28, 94), new ContainerDef(-1, 64208, 94) }, this); + this.bank = new Bank(this); + this.emoteStatus = new int[20]; + this.skills = new Skills(this); + this.uniqueId = -1; + this.specialEnergy = Settings.MAX_SPECIAL_ENERGY; + this.contacts = new Contacts(this); + this.playerStatus = 0; + this.bonuses = new PlayerBonuses(this); + this.settings = new PlayerSettings(this); + this.loadedPosition = null; + this.npcId = -1; + this.equippedAttack = EquippedWeapon.getFists(); + this.localGroundItems = new LinkedList(); + this.prayers = new HashMap(); + for (Prayer prayer : Prayer.values()) + if (!this.prayers.containsKey(prayer.getType())) + this.prayers.put(prayer.getType(), null); + } + + /** + * Login. + */ + public void login() { + synchronized (OsirisUpstreamHandler.getPlayerMap()) { + OsirisUpstreamHandler.getPlayerMap().put(channel.getId(), this); + } + + int playersOnline = Main.getPlayers().size(); + if (playersOnline > Main.getMostPlayersOnline()) { + Main.setMostPlayersOnline(playersOnline); + } + this.eventWriter.sendLandscape(); + showFrame(); + skills.initialize(); + + if (loadedPosition != null) { + teleport(loadedPosition); + } + + eventWriter.sendMessage("Welcome to " + Settings.SERVER_NAME + " created by Boomer216, Blakeman8192, and Samuraiblood2!"); + eventWriter.sendMessage("Want to play with more people? Visit www.enkrona.net!"); + inventory.refresh(); + equipment.refresh(); + bank.refresh(); + contacts.refresh(); + + for (int i = 0; i < Skills.NUMBER_OF_SKILLS; i++) + eventWriter.sendSkillLevel(i); + + sendEmoteStatus(); + getCombatManager().setAutoSpell(-1); + bonuses.update(); + + refreshPlayerOptions(); + + settings.refresh(); + setRunToggle(false); + + updateSpecEnergy(); + updateAttackStyle(); + eventWriter.sendEnergy(); + changeGender(appearance.getGender()); + contacts.setOnlineStatus(true); + + synchronized (Main.getPlayers()) { + updateAnimationIndices(); + // ^ will add an appearance block, so we don't need to. + getUpdateFlags().flag(UpdateFlags.UpdateFlag.TELEPORTED); + } + } + + public void refreshPlayerOptions() { + eventWriter.sendPlayerOption("Follow", 2, false); + eventWriter.sendPlayerOption("Trade with", 4, false); + } + + public void showFrame() { + eventWriter.sendWindowPane(548); + eventWriter.sendTab(6, 745); + eventWriter.sendTab(7, 754); + eventWriter.sendTab(68, ProtocolConstants.INTERFACE_CHATBOX); // Chatbox + eventWriter.sendConfig2(1160, -1); + eventWriter.sendTab(64, ProtocolConstants.INTERFACE_HP_BUBBLE); // HP + // bar + eventWriter.sendTab(65, ProtocolConstants.INTERFACE_PRAYER_BUBBLE); // Prayer + // bar + eventWriter.sendTab(66, ProtocolConstants.INTERFACE_ENERGY_BUBBLE); // Energy + // bar + eventWriter.sendTab(8, ProtocolConstants.INTERFACE_PLAYER_NAME); // Playername + // on + // chat + eventWriter.sendTab(11, ProtocolConstants.INTERFACE_CHAT_OPTIONS); // Chat + // options + refreshWeaponTab(); + eventWriter.sendTab(74, ProtocolConstants.INTERFACE_SKILL_TAB); // Skill + // tab + eventWriter.sendTab(75, ProtocolConstants.INTERFACE_QUEST_TAB); // Quest + // tab + eventWriter.sendTab(76, ProtocolConstants.INTERFACE_INVENTORY_TAB); // Inventory + // tab + eventWriter.sendTab(77, ProtocolConstants.INTERFACE_EQUIPMENT_TAB); // Equipment + // tab + eventWriter.sendTab(78, ProtocolConstants.INTERFACE_PRAYER_TAB); // Prayer + // tab + eventWriter.sendTab(79, getSpellBook().getInterfaceId()); // Magic tab + eventWriter.sendTab(81, ProtocolConstants.INTERFACE_FRIEND_TAB); // Friend + // tab + eventWriter.sendTab(82, ProtocolConstants.INTERFACE_IGNORE_TAB); // Ignore + // tab + eventWriter.sendTab(83, ProtocolConstants.INTERFACE_CLAN_TAB); // Clan + // tab + eventWriter.sendTab(84, ProtocolConstants.INTERFACE_SETTING_TAB); // Setting + // tab + eventWriter.sendTab(85, ProtocolConstants.INTERFACE_EMOTE_TAB); // Emote + // tab + eventWriter.sendTab(86, ProtocolConstants.INTERFACE_MUSIC_TAB); // Music + // tab + eventWriter.sendTab(87, ProtocolConstants.INTERFACE_LOGOUT_TAB); // Logout + // tab + } + + /** + * Gets the emote status. + * + * @return the emote status + */ + public int[] getEmoteStatus() { + return emoteStatus; + } + + /** + * Force appearance update. + */ + public void updateAppearance() { + addUpdateBlock(new AppearanceBlock(this)); + } + + /** + * Logout. + */ + public void logout() { + if (!Main.getPlayers().contains(this)) + return; + try { + contacts.setOnlineStatus(false); + if (this.getCurrentAction() != null) + this.getCurrentAction().cancel(); + if (getMovementQueue().isLocked()) { + Position last = getMovementQueue().getLastPosition(); + if (last != null) + teleport(getMovementQueue().getLastPosition()); + } + if (PlayerData.save(this)) + if (Main.isLocal()) + System.out.println("Game Saved for: " + this.getUsername()); + else if (Main.isLocal()) + System.out.println("Game NOT Saved for: " + this.getUsername()); + if (Main.isLocal()) + System.out.println(this + " logging out."); + } catch (Exception ex) { + ex.printStackTrace(); + } finally { + eventWriter.sendLogout(); + synchronized (Main.getPlayers()) { + Main.getPlayers().remove(this); + + } + synchronized (OsirisUpstreamHandler.getPlayerMap()) { + OsirisUpstreamHandler.getPlayerMap().remove(channel); + } + } + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "Player(" + username + ":" + password + ")#" + getSlot(); + } + + /** + * Sets the channel. + * + * @param channel + * the channel to set + */ + public void setChannel(Channel channel) { + this.channel = channel; + } + + /** + * Gets the channel. + * + * @return the channel + */ + public Channel getChannel() { + return channel; + } + + /** + * Sets the username as long. + * + * @param usernameAsLong + * the new username as long + */ + public void setUsernameAsLong(long usernameAsLong) { + this.usernameAsLong = usernameAsLong; + } + + /** + * Gets the username as long. + * + * @return the username as long + */ + public long getUsernameAsLong() { + return usernameAsLong; + } + + /** + * Sets the username. + * + * @param username + * the username to set + */ + public void setUsername(String username) { + this.username = username; + } + + /** + * Gets the username. + * + * @return the username + */ + public String getUsername() { + return username; + } + + /** + * Sets the password. + * + * @param password + * the password to set + */ + public void setPassword(String password) { + this.password = password; + } + + /** + * Gets the password. + * + * @return the password + */ + public String getPassword() { + return password; + } + + /** + * Gets the event writer. + * + * @return the eventWriter + */ + public EventWriter getEventWriter() { + return eventWriter; + } + + /** + * Gets the inventory. + * + * @return the inventory + */ + public ItemContainer getInventory() { + return inventory; + } + + /** + * Gets the bank. + * + * @return the bank + */ + public Bank getBank() { + return bank; + } + + /** + * Gets the equipment. + * + * @return the equipment + */ + public ItemContainer getEquipment() { + return equipment; + } + + /** + * Gets the appearance. + * + * @return the appearance + */ + public Appearance getAppearance() { + return appearance; + } + + public PlayerSettings getSettings() { + return settings; + } + + /** + * Adds the all items. + * + * @param items + * the items + */ + public void addAllItems(Item[] items) { + for (Item item : items) { + if (item != null) + addItem(item); + } + } + + /** + * Adds the item. + * + * @param item + * the item + */ + public void addItem(Item item) { + if (item.getAmount() == 0) + return; + if (!getInventory().add(item) && getPlayerStatus() < 2) { + GroundItem ground = new GroundItem(item, this); + GroundManager.getManager().dropItem(ground); + } + } + + /** + * Update animation indices. + */ + public void updateAnimationIndices() { + if (getEquippedAttack() != null) { + int[] indices = getEquippedAttack().getMobSprites(); + setStandAnimation(indices[0]); + setWalkAnimation(indices[1]); + setRunAnimation(indices[2]); + } else { + setStandAnimation(AppearanceBlock.STAND_INDICE); + setWalkAnimation(AppearanceBlock.WALK_INDICE); + setRunAnimation(AppearanceBlock.RUN_INDICE); + } + addUpdateBlock(new AppearanceBlock(this)); + } + + /** + * Equip item. + * + * @param itemId + * the item id + * @param slot + * the slot + */ + public void equipItem(int itemId, int slot) { + Item toEquip = inventory.getItem(slot); + if (toEquip == null) + return; + int toEquipId = toEquip.getId(); + if (toEquipId != itemId) + return; + ItemDef def = ItemDef.forId(toEquip.getId()); + if (def == null) + return; + + boolean equipmentScreen = getCurrentAction() != null && getCurrentAction() instanceof EquipmentScreenAction; + if (getCurrentAction() != null) { + if (!equipmentScreen) + getCurrentAction().cancel(); + } + int equipSlot = def.getItemType(); + int[] requirements = def.getRequirements(); + boolean meetsRequirements = true; + if (requirements != null) { + for (int i = 0; i < requirements.length; i++) { + int level = skills.maxLevel(i); + if (level < requirements[i]) { + getEventWriter().sendMessage("You need level " + requirements[i] + " " + Skills.SKILL_NAMES[i] + " to equip this item!"); + meetsRequirements = false; + } + } + } + + if (!meetsRequirements) + return; + Item equippedItem = equipment.getItem(equipSlot); + Item shield = equipment.getItem(Settings.SLOT_SHIELD); + Item weapon = equipment.getItem(Settings.SLOT_WEAPON); + + if (def.isTwoHanded()) { + if (inventory.isFull() && shield != null) { + getEventWriter().sendMessage("You don't have enough room in your inventory to do that!"); + return; + } + + equipment.set(equipSlot, toEquip); + int slotOfItem = -1; + if (equippedItem != null) + slotOfItem = inventory.getSlotById(equippedItem.getId()); + if (slotOfItem != -1 && ItemDef.forId(equippedItem.getId()).isStackable()) { + inventory.set(slotOfItem, Item.create(equippedItem.getId(), equippedItem.getAmount() + inventory.getItem(slotOfItem).getAmount())); + inventory.set(slot, null); + } else + inventory.set(slot, equippedItem); + if (shield != null) { + unequipItem(shield.getId(), Settings.SLOT_SHIELD); + } + } else if (def.isShield() && weapon != null) { + equipment.set(equipSlot, toEquip); + inventory.set(slot, equippedItem); + if (ItemDef.forId(weapon.getId()).isTwoHanded()) { + unequipItem(weapon.getId(), Settings.SLOT_WEAPON); + } + updateAnimationIndices(); + } else if (def.isStackable() && equippedItem != null && equippedItem.getId() == toEquipId) { + equipment.set(equipSlot, Item.create(toEquipId, (toEquip.getAmount() + equippedItem.getAmount()))); + inventory.set(slot, null); + } else { + equipment.set(equipSlot, toEquip); + int slotOfItem = -1; + if (equippedItem != null) + slotOfItem = inventory.getSlotById(equippedItem.getId()); + if (slotOfItem != -1 && ItemDef.forId(equippedItem.getId()).isStackable()) { + inventory.set(slotOfItem, Item.create(equippedItem.getId(), equippedItem.getAmount() + inventory.getItem(slotOfItem).getAmount())); + inventory.set(slot, null); + } else + inventory.set(slot, equippedItem); + } + bonuses.update(); + if (equipSlot == Settings.SLOT_WEAPON) { + setSpecEnabled(false); + refreshWeaponTab(); + getCombatManager().setAutoSpell(-1); + } + updateAnimationIndices(); + if (equipmentScreen) + getEventWriter().sendInventoryInterface(149); + } + + /** + * Unequip item. + * + * @param itemId + * the item id + * @param slot + * the slot + */ + public void unequipItem(int itemId, int slot) { + Item equippedItem = equipment.getItem(slot); + if (equippedItem == null) + return; + if (equippedItem.getId() != itemId) + return; + boolean equipmentScreen = getCurrentAction() != null && getCurrentAction() instanceof EquipmentScreenAction; + if (getCurrentAction() != null) { + if (!equipmentScreen) + getCurrentAction().cancel(); + } + if (inventory.getEmptySlot() == -1 && !(ItemDef.forId(equippedItem.getId()).isStackable() && inventory.getSlotById(equippedItem.getId()) != -1)) { + getEventWriter().sendMessage("You don't have enough room!"); + return; + } + equipment.set(slot, null); + inventory.add(equippedItem.getId(), equippedItem.getAmount()); + bonuses.update(); + if (slot == Settings.SLOT_WEAPON) { + setSpecEnabled(false); + refreshWeaponTab(); + getCombatManager().setAutoSpell(-1); + } + updateAnimationIndices(); + if (equipmentScreen) + getEventWriter().sendInventoryInterface(149); + } + + /** + * Switch items. + * + * @param fromSlot + * the from slot + * @param toSlot + * the to slot + */ + public void switchItems(int fromSlot, int toSlot) { + inventory.move(fromSlot, toSlot); + } + + /** + * Gets the local npcs. + * + * @return the localNpcs + */ + public List getLocalNpcs() { + return localNpcs; + } + + /** + * One button. + * + * @return true, if successful + */ + public boolean oneButton() { + return oneButton; + } + + /** + * Sets the one button. + * + * @param oneButton + * the new one button + */ + public void setOneButton(boolean oneButton) { + this.oneButton = oneButton; + } + + /** + * Gets the items kept on death. + * + * @return the items kept on death + */ + public ArrayList getItemsKeptOnDeath() { + PriorityQueue allItems = new PriorityQueue(1, new Comparator() { + @Override + public int compare(Item a, Item b) { + int difference = ItemDef.forId(b.getId()).getPrice() - ItemDef.forId(a.getId()).getPrice(); + return difference; + } + }); + Item[] items = new Item[equipment.getLength() + inventory.getLength()]; + System.arraycopy(equipment.getItems(), 0, items, 0, equipment.getLength()); + System.arraycopy(inventory.getItems(), 0, items, equipment.getLength(), inventory.getLength()); + for (Item item : items) { + if (item == null) + continue; + if (ItemDef.forId(item.getId()).isTradeable()) + allItems.offer(Item.create(item.getId())); + } + ArrayList keptItems = new ArrayList(); + int itemsKept = 3; + PrayerEffect salvage = getPrayers().get(Prayer.Type.SALVAGE); + if (salvage != null) + itemsKept += 1; + while (keptItems.size() < itemsKept && allItems.size() > 0) { + keptItems.add(allItems.poll()); + } + return keptItems; + } + + /** + * Deposit item. + * + * @param itemSlot + * the item slot + * @param itemN + * the item n + */ + public void depositItem(int itemSlot, int itemN) { + if (getCurrentAction() == null || !(getCurrentAction() instanceof BankAction)) + return; + Item item = inventory.getItem(itemSlot); + if (item == null) + return; + int itemId = item.getId(); + int hasAmount = getInventory().amountOfItem(item.getId()); + if (itemN > hasAmount) + itemN = hasAmount; + if (getBank().canFit(itemId, itemN) && getInventory().removeBySlot(itemSlot, itemN)) + getBank().add(itemId, itemN); + } + + /** + * Withdraw item. + * + * @param itemSlot + * the item slot + * @param itemN + * the item n + */ + public void withdrawItem(int itemSlot, int itemN) { + if (getCurrentAction() == null || !(getCurrentAction() instanceof BankAction)) + return; + int tab = bank.getTabBySlot(itemSlot); + Item item; + if (tab == -1) + item = bank.getMain().getItem(itemSlot); + else + item = bank.getTabs().get(tab).getItem(itemSlot); + if (item == null) + return; + int itemId = item.getId(); + int hasAmount = item.getAmount(); + if (itemN > hasAmount) + itemN = hasAmount; + int amountOfRoom = getInventory().countEmptySlots(); + if (getBank().withdrawNotes() && ItemDef.forId(itemId + 1).isNoted()) + itemId += 1; + ItemDef def = ItemDef.forId(itemId); + if ((!def.isStackable() && amountOfRoom < itemN) || (def.isStackable() && amountOfRoom == 0)) + itemN = amountOfRoom; + if (getBank().remove(itemSlot, itemN)) { + getInventory().add(itemId, itemN); + } + } + + /** + * Sets the hd. + * + * @param isHd + * the isHd to set + */ + public void setHd(boolean isHd) { + this.isHd = isHd; + } + + /** + * Checks if is hd. + * + * @return the isHd + */ + public boolean isHd() { + return isHd; + } + + /** + * Sets the interface open. + * + * @param openInterface + * the new interface open + */ + public void setInterfaceOpen(int openInterface) { + this.interfaceOpen = openInterface; + } + + /** + * Gets the interface open. + * + * @return the interface open + */ + public int getInterfaceOpen() { + return interfaceOpen; + } + + /** + * Send emote status. + */ + public void sendEmoteStatus() { + int configBGPE = 0; + int flag; + for (int i = 0; i < Settings.BIG_GP_EMOTES.length; i++) { + if (emoteStatus[Settings.BIG_GP_EMOTES[i]] == 1) { + flag = 1 << i; + configBGPE += flag; + } + } + + int configLGPE = 0; + for (int i = 0; i < Settings.LITTLE_GP_EMOTES.length; i++) { + if (emoteStatus[Settings.LITTLE_GP_EMOTES[i]] == 1) { + flag = 1 << i; + configLGPE += flag; + } + } + + /* Delayed until after loops so they all appear closer to same time */ + + getEventWriter().sendConfig(802, configLGPE); + + getEventWriter().sendConfig(313, configBGPE); + + if (emoteStatus[Settings.GOBLIN_EMOTES[0]] == 1 && emoteStatus[Settings.GOBLIN_EMOTES[1]] == 1) + getEventWriter().sendConfig(465, 7); + + if (emoteStatus[Settings.EMOTE_ZOMBIE_HAND] == 1) + getEventWriter().sendConfig(1085, 12); + } + + /** + * Sets the last region. + * + * @param lastRegion + * the lastRegion to set + */ + public void setLastRegion(Position lastRegion) { + this.lastRegion = lastRegion; + } + + /** + * Gets the last region. + * + * @return the lastRegion + */ + public Position getLastRegion() { + return lastRegion; + } + + /** + * Gets the skills. + * + * @return the skills + */ + public Skills getSkills() { + return skills; + } + + /** + * Gets the forum index. + * + * @return the forum index + */ + public int getUniqueId() { + return uniqueId; + } + + public void setUniqueId(int uniqueId) { + this.uniqueId = uniqueId; + } + + /** + * Gets the player updater. + * + * @return the player updater + */ + public PlayerUpdater getPlayerUpdater() { + return playerUpdater; + } + + /** + * Gets the npc updater. + * + * @return the npc updater + */ + public NpcUpdater getNpcUpdater() { + return npcUpdater; + } + + /** + * Gets the attack style. + * + * @return the attack style + */ + public int getAttackStyle() { + return attackStyle; + } + + /** + * Sets the attack style. + * + * @param style + * the new attack style + */ + public void setAttackStyle(int style, boolean refresh) { + if (this.equippedAttack != null && (equippedAttack.getTabId() == 77 || equippedAttack.getTabId() == 79)) { + if (style == 1) + style = equippedAttack.getTabId() == 76 ? 3 : 2; + else if (style == 2) + style = 1; + } + this.attackStyle = style; + if (refresh) + updateAttackStyle(); + } + + public void updateAttackStyle() { + getEventWriter().sendConfig(43, attackStyle); + } + + /** + * Sets the players run energy. + * + * @param energy + * The players run energy. + */ + @Override + public void setEnergy(int energy, boolean send) { + super.setEnergy(energy, send); + if (send) + eventWriter.sendEnergy(); + } + + /** + * Sets whether or not the run button is toggled. + * + * @param runToggle + * Whether or not the run button is toggled. + */ + @Override + public void setRunToggle(boolean runToggle) { + super.setRunToggle(runToggle); + if (runToggle) { + eventWriter.sendConfig(173, 1); + } else { + eventWriter.sendConfig(173, 0); + } + } + + /** + * Gets the player status. + * + * @return the player status + */ + public int getPlayerStatus() { + return playerStatus; + } + + /** + * Sets the trade request. + * + * @param player + * the new trade request + */ + public void setTradeRequest(Player player) { + this.tradeRequest = player; + } + + /** + * Gets the trade request. + * + * @return the trade request + */ + public Player getTradeRequest() { + return tradeRequest; + } + + /** + * Gets the bonuses. + * + * @return the bonuses + */ + public PlayerBonuses getBonuses() { + return bonuses; + } + + /** + * Gets the friends. + * + * @return the friends + */ + public Contacts getContacts() { + return contacts; + } + + /** + * Gets the trade open. + * + * @return the trade open + */ + public Trade getTradeOpen() { + Action action = getCurrentAction(); + if (action == null || !(action instanceof TradeAction)) + return null; + return ((TradeAction) action).getTrade(); + } + + /** + * Equip weapon. + * + * @param itemId + * the item id + */ + public void equipWeapon(int itemId) { + String weapon = null; + if (itemId != -1) + weapon = ItemDef.forId(equipment.getItem(Settings.SLOT_WEAPON).getId()).getName(); + equippedAttack = EquippedWeapon.getType(weapon); + int amountOfStyles = equippedAttack.getSkills().length; + if (this.attackStyle >= amountOfStyles) + setAttackStyle((amountOfStyles - 1), true); + getEventWriter().sendTab(/* player.isHd() ? 87 : */73, equippedAttack.getTabId()); + String wepName = weapon == null ? "Unarmed" : weapon; + getEventWriter().sendString(wepName, equippedAttack.getTabId(), 0); + updateSpecBar(); + updateAppearance(); + } + + /** + * Update spec bar. + */ + public void updateSpecBar() { + if (equippedAttack == null) + return; + int childId = equippedAttack.getSpecButton(); + if (childId == -1) + return; + if (equipment.getItem(Settings.SLOT_WEAPON) == null || SpecialManager.specWeaponIndex(equipment.getItem(Settings.SLOT_WEAPON).getId()) == -1) { + eventWriter.sendInterfaceConfig(equippedAttack.getTabId(), childId, true); + return; + } + eventWriter.sendInterfaceConfig(equippedAttack.getTabId(), childId, false); + } + + /** + * Update spec energy. + */ + public void updateSpecEnergy() { + eventWriter.sendConfig(300, this.getSpecialEnergy()); + } + + /** + * Gets the equipped attack. + * + * @return the equipped attack + */ + public EquippedWeapon getEquippedAttack() { + return equippedAttack; + } + + /** + * Gets the special energy. + * + * @return the special energy + */ + public int getSpecialEnergy() { + return specialEnergy; + } + + /** + * Sets the special energy. + * + * @param energy + * the new special energy + */ + public void setSpecialEnergy(int energy, boolean send) { + this.specialEnergy = energy; + if (send) + updateSpecEnergy(); + } + + /** + * Adjust special energy. + * + * @param adjustment + * the adjustment + * @return true, if successful + */ + public boolean adjustSpecialEnergy(int adjustment) { + if (adjustment < 0 && (specialEnergy + adjustment < 0)) { + return false; + } else if (adjustment > 0 && (specialEnergy + adjustment > Settings.MAX_SPECIAL_ENERGY)) + specialEnergy = Settings.MAX_SPECIAL_ENERGY; + else { + if (adjustment > 0 || (adjustment < 0 && getPlayerStatus() != 2)) + specialEnergy += adjustment; + } + updateSpecEnergy(); + return true; + } + + /** + * Special regain rate. + * + * @return the int + */ + public int specialRegainRate() { + return 3; + } + + /** + * Checks if is spec enabled. + * + * @return true, if is spec enabled + */ + public boolean isSpecEnabled() { + return specEnabled; + } + + /** + * Sets the spec enabled. + * + * @param enabled + * the new spec enabled + */ + public void setSpecEnabled(boolean enabled) { + Item itemEquipped = equipment.getItem(Settings.SLOT_WEAPON); + if (itemEquipped == null) + return; + int specIndex = SpecialManager.specWeaponIndex(itemEquipped.getId()); + if (enabled) { + if (specIndex == -1) + return; + if ((SpecialManager.ENERGY_REQUIRED[specIndex] * 1000) > getSpecialEnergy()) { + eventWriter.sendMessage("You do not have enough energy!"); + return; + } + } + eventWriter.sendConfig(301, enabled ? 1 : 0); + specEnabled = enabled; + } + + /** + * Sets the stand animation. + * + * @param standAnimation + * the new stand animation + */ + public void setStandAnimation(int standAnimation) { + this.standAnimation = standAnimation; + } + + /** + * Gets the stand animation. + * + * @return the stand animation + */ + public int getStandAnimation() { + return standAnimation; + } + + /** + * Sets the walk animation. + * + * @param walkAnimation + * the new walk animation + */ + public void setWalkAnimation(int walkAnimation) { + this.walkAnimation = walkAnimation; + } + + /** + * Gets the walk animation. + * + * @return the walk animation + */ + public int getWalkAnimation() { + return walkAnimation; + } + + /** + * Sets the run animation. + * + * @param runAnimation + * the new run animation + */ + public void setRunAnimation(int runAnimation) { + this.runAnimation = runAnimation; + } + + /** + * Gets the run animation. + * + * @return the run animation + */ + public int getRunAnimation() { + return runAnimation; + } + + /** + * Gets the exp rate. + * + * @return the exp rate + */ + public double getExpRate() { + return Settings.EXP_RATE; + } + + public void setEmoteStatus(int[] emoteStatus, boolean send) { + this.emoteStatus = emoteStatus; + if (send) + sendEmoteStatus(); + } + + /* + * (non-Javadoc) + * + * @see osiris.game.model.Character#teleport(osiris.game.model.Position) + */ + @Override + public void teleport(Position to) { + getMovementQueue().reset(); + setPosition(to.clone()); + getUpdateFlags().flag(UpdateFlag.TELEPORTED); + } + + public void setHeadIcon(int headIcon) { + this.headIcon = headIcon; + addUpdateBlock(new AppearanceBlock(this)); + } + + public int getHeadIcon() { + return headIcon; + } + + public void setSkullIcon(int skullIcon) { + this.skullIcon = skullIcon; + addUpdateBlock(new AppearanceBlock(this)); + } + + public int getSkullIcon() { + return skullIcon; + } + + public void setPlayerStatus(int status) { + this.playerStatus = status; + } + + public int getNpcId() { + return npcId; + } + + public void setLoadedPosition(Position loaded) { + this.loadedPosition = loaded; + } + + public void refreshWeaponTab() { + Item weapon = getEquipment().getItem(Settings.SLOT_WEAPON); + if (weapon == null) + equipWeapon(-1); + else + equipWeapon(weapon.getId()); + } + + public StopWatch getTimeoutTimer() { + return timeoutTimer; + } + + public XValue getXValue() { + return xValue; + } + + public void setXValue(XValue xValue) { + this.xValue = xValue; + } + + public void changeGender(int gender) { + getAppearance().setGender(gender); + updateAppearance(); + } + + public void setCurrentlyOpenDialogue(Dialogue dialogue) { + this.dialogue = dialogue; + } + + public Dialogue getCurrentlyOpenDialogue() { + return dialogue; + } + + public void transform(int toNpcId) { + this.npcId = toNpcId; + updateAnimationIndices(); + } + + public LinkedList getGroundItems() { + return localGroundItems; + } + + public HashMap getPrayers() { + return prayers; + } + + public double getPrayerDrainRate() { + double prayerDrain = 0; + for (PrayerEffect effect : getPrayers().values()) + if (effect != null) + prayerDrain += effect.getPrayer().getDrainRate(); + return prayerDrain; + } +} diff --git a/src/osiris/game/model/PlayerBonuses.java b/src/osiris/game/model/PlayerBonuses.java new file mode 100644 index 0000000..67495c4 --- /dev/null +++ b/src/osiris/game/model/PlayerBonuses.java @@ -0,0 +1,94 @@ +package osiris.game.model; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.model.def.ItemDef; +import osiris.game.model.item.Item; +import osiris.util.Settings; + +// TODO: Auto-generated Javadoc +/** + * The Class PlayerBonuses. + * + * @author Boomer + */ +public class PlayerBonuses { + + /** The player. */ + private final Player player; + + /** + * Instantiates a new player bonuses. + * + * @param player + * the player + */ + public PlayerBonuses(Player player) { + this.player = player; + } + + /** The bonuses. */ + private int[] bonuses = new int[Settings.BONUSES.length]; + + /** + * Update. + */ + public void update() { + for (int n = 0; n < bonuses.length; n++) { + bonuses[n] = 0; + } + for (int i = 0; i < player.getEquipment().getLength(); i++) { + Item item = player.getEquipment().getItem(i); + if (item == null) + continue; + ItemDef def = ItemDef.forId(item.getId()); + if (def.getBonuses() != null) { + for (int n = 0; n < bonuses.length; n++) { + bonuses[n] += def.getBonus(n); + } + } + } + + player.getEventWriter().sendBonus(bonuses); + } + + /** + * Sets the. + * + * @param slot + * the slot + * @param value + * the value + */ + public void set(int slot, int value) { + bonuses[slot] = value; + } + + /** + * Gets the. + * + * @param slot + * the slot + * @return the int + */ + public int get(int slot) { + return bonuses[slot]; + } + +} \ No newline at end of file diff --git a/src/osiris/game/model/PlayerSettings.java b/src/osiris/game/model/PlayerSettings.java new file mode 100644 index 0000000..f6c82ec --- /dev/null +++ b/src/osiris/game/model/PlayerSettings.java @@ -0,0 +1,181 @@ +package osiris.game.model; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +// TODO: Auto-generated Javadoc +/** + * The Class PlayerSettings. + * + * @author Boomer + * @author samuraiblood2 + * + */ +public class PlayerSettings { + + /** The auto retaliate. */ + private boolean autoRetaliate; + + /** The split private chat. */ + private boolean splitPrivateChat; + + /** The trade chat. */ + private int publicChat, privateChat, tradeChat; + + /** The chat effects. */ + private boolean chatEffects; + + /** The player. */ + private final Player player; + + /** The Constant CHAT_HIDE. */ + public static final int CHAT_ON = 0, CHAT_FRIENDS = 1, CHAT_OFF = 2, CHAT_HIDE = 3; + + /** The Constant CHAT_SLOT_TRADE. */ + public static final int CHAT_SLOT_PUBLIC = 0, CHAT_SLOT_PRIVATE = 1, CHAT_SLOT_TRADE = 2; + + /** + * Instantiates a new player settings. + * + * @param player + * the player + */ + public PlayerSettings(Player player) { + this.player = player; + setDefault(); + publicChat = CHAT_ON; + privateChat = CHAT_ON; + tradeChat = CHAT_ON; + } + + /** + * Refresh. + */ + public void refresh() { + if (splitPrivateChat) { + player.getEventWriter().sendConfig(287, 0); + } else { + player.getEventWriter().runScript(83, "s"); + player.getEventWriter().sendConfig(287, 1); + } + player.getEventWriter().sendConfig(171, chatEffects ? 0 : 1); + player.getEventWriter().sendConfig(172, autoRetaliate ? 0 : 1); + player.getEventWriter().sendChatOptions(publicChat, privateChat, tradeChat); + // player.showFrame(false); + } + + /** + * Sets the default. + */ + public void setDefault() { + autoRetaliate = false; + splitPrivateChat = false; + chatEffects = true; + } + + /** + * Checks if is auto retaliate. + * + * @return true, if is auto retaliate + */ + public boolean isAutoRetaliate() { + return autoRetaliate; + } + + /** + * Sets the auto retaliate. + * + * @param autoRetaliate + * the new auto retaliate + */ + public void setAutoRetaliate(boolean autoRetaliate) { + this.autoRetaliate = autoRetaliate; + } + + /** + * Checks if is split private chat. + * + * @return true, if is split private chat + */ + public boolean isSplitPrivateChat() { + return splitPrivateChat; + } + + /** + * Sets the split private chat. + * + * @param splitPrivateChat + * the new split private chat + */ + public void setSplitPrivateChat(boolean splitPrivateChat) { + this.splitPrivateChat = splitPrivateChat; + } + + /** + * Checks if is chat effects. + * + * @return true, if is chat effects + */ + public boolean isChatEffects() { + return chatEffects; + } + + /** + * Sets the chat effects. + * + * @param chatEffects + * the new chat effects + */ + public void setChatEffects(boolean chatEffects) { + this.chatEffects = chatEffects; + } + + /** + * Sets the chat options. + * + * @param publicChat + * the public chat + * @param privateChat + * the private chat + * @param tradeChat + * the trade chat + */ + public void setChatOptions(int publicChat, int privateChat, int tradeChat) { + this.publicChat = publicChat; + this.privateChat = privateChat; + this.tradeChat = tradeChat; + } + + /** + * Gets the chat options. + * + * @return the chat options + */ + public int[] getChatOptions() { + return new int[] { publicChat, privateChat, tradeChat }; + } + + /** + * Gets the public chat. + * + * @return the public chat + */ + public int getPublicChat() { + return publicChat; + } +} diff --git a/src/osiris/game/model/Position.java b/src/osiris/game/model/Position.java new file mode 100644 index 0000000..3a23db0 --- /dev/null +++ b/src/osiris/game/model/Position.java @@ -0,0 +1,302 @@ +package osiris.game.model; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import static osiris.util.Settings.DEFAULT_X; +import static osiris.util.Settings.DEFAULT_Y; +import static osiris.util.Settings.DEFAULT_Z; + +// TODO: Auto-generated Javadoc + +/** + * A position with methods for getting region and other coordinate data. + * + * @author Blake + * + */ +public class Position { + + /** + * The x. + */ + private int x; + + /** + * The y. + */ + private int y; + + /** + * The z. + */ + private int z; + + /** + * Instantiates a new position. + */ + public Position() { + this(DEFAULT_X, DEFAULT_Y); + } + + /** + * Instantiates a new position. + * + * @param x + * the x + * @param y + * the y + */ + public Position(int x, int y) { + this(x, y, DEFAULT_Z); + } + + /** + * Instantiates a new position. + * + * @param x + * the x + * @param y + * the y + * @param z + * the z + */ + public Position(int x, int y, int z) { + this.x = x; + this.y = y; + this.z = z; + } + + /** + * Instantiates a new position. + * + * @param position + * the position + */ + public Position(Position position) { + this.x = position.x; + this.y = position.y; + this.z = position.z; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "Position[absolute=" + x + ", " + y + ", " + z + "]" + "[local=" + getLocalX() + ", " + getLocalY() + "]" + "[region=" + getRegionX() + ", " + getRegionY() + "]"; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object other) { + if (other instanceof Position) { + Position p = (Position) other; + return (p.x == x && p.y == y && p.z == z); + } + return false; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#clone() + */ + @Override + public Position clone() { + return new Position(x, y, z); + } + + /** + * Sets the. + * + * @param position + * the position + */ + public void set(Position position) { + this.x = position.x; + this.y = position.y; + this.z = position.z; + } + + /** + * Sets the. + * + * @param x + * the x + * @param y + * the y + */ + public void set(int x, int y) { + set(x, y, z); + } + + /** + * Sets the. + * + * @param x + * the x + * @param y + * the y + * @param z + * the z + */ + public void set(int x, int y, int z) { + this.x = x; + this.y = y; + this.z = z; + } + + /** + * Checks if a Position is in sight. + * + * @param other + * the other + * @return true, if the argued Position is in sight + */ + public boolean inSight(Position other, int radius) { + if (other.z == z) { + int deltaX = other.x - x; + int deltaY = other.y - y; + return deltaX < radius && deltaX >= -radius && deltaY < radius && deltaY >= -radius; + } + return false; + } + + /** + * Gets the region x. + * + * @return the region x + */ + public int getRegionX() { + return x >> 3; + } + + /** + * Gets the region y. + * + * @return the region y + */ + public int getRegionY() { + return y >> 3; + } + + /** + * Gets the local x. + * + * @return the local x + */ + public int getLocalX() { + return getLocalX(this); + } + + /** + * Gets the local y. + * + * @return the local y + */ + public int getLocalY() { + return getLocalY(this); + } + + /** + * Gets the local x. + * + * @param relative + * the relative + * @return the local x + */ + public int getLocalX(Position relative) { + return x - 8 * (relative.getRegionX() - 6); + } + + /** + * Gets the local y. + * + * @param relative + * the relative + * @return the local y + */ + public int getLocalY(Position relative) { + return y - 8 * (relative.getRegionY() - 6); + } + + /** + * Sets the x. + * + * @param x + * the x to set + */ + public void setX(int x) { + this.x = x; + } + + /** + * Gets the x. + * + * @return the x + */ + public int getX() { + return x; + } + + /** + * Sets the y. + * + * @param y + * the y to set + */ + public void setY(int y) { + this.y = y; + } + + /** + * Gets the y. + * + * @return the y + */ + public int getY() { + return y; + } + + /** + * Sets the z. + * + * @param z + * the z to set + */ + public void setZ(int z) { + this.z = z; + } + + /** + * Gets the z. + * + * @return the z + */ + public int getZ() { + return z; + } + +} diff --git a/src/osiris/game/model/Skills.java b/src/osiris/game/model/Skills.java new file mode 100644 index 0000000..1780c32 --- /dev/null +++ b/src/osiris/game/model/Skills.java @@ -0,0 +1,392 @@ +package osiris.game.model; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.util.Arrays; + +import osiris.game.update.block.GraphicsBlock; +import osiris.util.Settings; +import osiris.util.Utilities; + +// TODO: Auto-generated Javadoc + +/** + * The Class Skills. + * + * @author Boomer + * + */ +public class Skills { + + /** The recovery times */ + private int[] recovery; + + /** + * The experience. + */ + private double[] curLevel, experience; + + /** + * The level. + */ + private int[] maxLevel; + + /** + * The player. + */ + private final Player player; + + /* The player's combat level */ + private int combatLevel; + + /** + * Instantiates a new skills. + * + * @param player + * the player + */ + public Skills(Player player) { + this.player = player; + this.experience = new double[NUMBER_OF_SKILLS]; + this.curLevel = new double[NUMBER_OF_SKILLS]; + this.maxLevel = new int[NUMBER_OF_SKILLS]; + this.recovery = new int[NUMBER_OF_SKILLS]; + Arrays.fill(recovery, -1); + Arrays.fill(curLevel, 1); + Arrays.fill(maxLevel, 1); + curLevel[SKILL_HITPOINTS] = 10; + maxLevel[SKILL_HITPOINTS] = (int) curLevel[SKILL_HITPOINTS]; + experience[SKILL_HITPOINTS] = Utilities.expForLevel(maxLevel[SKILL_HITPOINTS]); + setCombatLevel(); + } + + /** + * sets the max levels on login + */ + public void initialize() { + for (int i = 0; i < NUMBER_OF_SKILLS; i++) + maxLevel[i] = Utilities.levelForExp(experience[i]); + setCombatLevel(); + } + + /** + * Current level. + * + * @param skillId + * the skill id + * @return the int + */ + public int currentLevel(int skillId) { + return (int) curLevel[skillId]; + } + + public double realCurrentLevel(int skillId) { + return curLevel[skillId]; + } + + /** + * Max level. + * + * @param skillId + * the skill id + * @return the int + */ + public int maxLevel(int skillId) { + return maxLevel[skillId]; + } + + /** + * Sets the level. + * + * @param skillId + * the skill id + * @param level + * the level + */ + public void setCurLevel(int skillId, double level) { + this.curLevel[skillId] = level; + player.getEventWriter().sendSkillLevel(skillId); + } + + /** + * Gets the exp. + * + * @param skillId + * the skill id + * @return the exp + */ + public double getExp(int skillId) { + return experience[skillId]; + } + + public int[] getRecoveryTimes() { + return recovery; + } + + public int[] getCurLevels() { + int[] levels = new int[curLevel.length]; + for (int i = 0; i < curLevel.length; i++) + levels[i] = (int) curLevel[i]; + return levels; + } + + public int[] getMaxLevels() { + int[] levels = new int[maxLevel.length]; + for (int i = 0; i < maxLevel.length; i++) + levels[i] = maxLevel[i]; + return levels; + } + + public double[] getExperience() { + return experience; + } + + public void setSkill(int skillId, double curLevel, double experience, boolean refresh) { + this.curLevel[skillId] = curLevel; + this.maxLevel[skillId] = Utilities.levelForExp(experience); + this.experience[skillId] = experience; + if (refresh) { + player.getEventWriter().sendSkillLevel(skillId); + player.updateAppearance(); + } + } + + public void addExp(int skillId, double exp) { + addExp(skillId, exp, true); + } + + /** + * Adds the exp. + * + * @param skillId + * the skill id + * @param exp + * the exp + */ + public void addExp(int skillId, double exp, boolean useBonusRate) { + + double expEarned = exp; + if (useBonusRate) + expEarned *= player.getExpRate(); + double expToAdd = ((Double.MAX_VALUE - experience[skillId]) < expEarned) ? Double.MAX_VALUE : experience[skillId] + expEarned; + setExp(skillId, expToAdd); + int levelForExp = Utilities.levelForExp(experience[skillId]); + if (maxLevel[skillId] < levelForExp) { + curLevel[skillId] = levelForExp; + maxLevel[skillId] = (int) curLevel[skillId]; + String skillName = SKILL_NAMES[skillId]; + boolean startsWithVowel = false; + String[] vowels = { "a", "e", "i", "o", "u" }; + for (String v : vowels) + if (skillName.startsWith(v)) + startsWithVowel = true; + player.getEventWriter().sendMessage("You have just advanced a" + (startsWithVowel ? "n" : "") + " " + skillName + " level! You have reached level " + maxLevel[skillId] + "."); + player.addUpdateBlock(new GraphicsBlock(player, 199, 100)); + player.getEventWriter().sendString("You have now reached level " + maxLevel[skillId] + ".", 740, 1); + if (maxLevel[skillId] == 99 && player.getEmoteStatus()[Settings.EMOTE_SKILL_CAPE] == 0) { + player.getEmoteStatus()[Settings.EMOTE_SKILL_CAPE] = 1; + player.sendEmoteStatus(); + } + setCombatLevel(); + player.getEventWriter().sendSkillLevel(skillId); + player.updateAppearance(); + } + } + + /** + * Sets the exp. + * + * @param skillId + * the skill id + * @param exp + * the exp + */ + public void setExp(int skillId, double exp) { + experience[skillId] = exp; + player.getEventWriter().sendSkillLevel(skillId); + } + + public void setCombatLevel() { + this.combatLevel = calculateCombatLevel(); + } + + public int getCombatLevel() { + return combatLevel; + } + + /** + * Gets the combat level. + * + * @return the combat level + */ + public int calculateCombatLevel() { + int attack = Utilities.levelForExp(experience[SKILL_ATTACK]); + int defence = Utilities.levelForExp(experience[SKILL_DEFENCE]); + int strength = Utilities.levelForExp(experience[SKILL_STRENGTH]); + int hp = Utilities.levelForExp(experience[SKILL_HITPOINTS]); + int prayer = Utilities.levelForExp(experience[SKILL_PRAYER]); + int ranged = Utilities.levelForExp(experience[SKILL_RANGE]); + int magic = Utilities.levelForExp(experience[SKILL_MAGIC]); + int combatLevel = 3; + combatLevel = (int) ((defence + hp + Math.floor(prayer / 2)) * 0.25) + 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) { + combatLevel += melee; + } else if (ranger >= melee && ranger >= mage) { + combatLevel += ranger; + } else if (mage >= melee && mage >= ranger) { + combatLevel += mage; + } + int summoning = Utilities.levelForExp(experience[SKILL_SUMMONING]); + summoning /= 8; + return combatLevel + summoning; + } + + /** + * The Constant NUMBER_OF_SKILLS. + */ + public static final int NUMBER_OF_SKILLS = 24; + + /** + * The Constant SKILL_SUMMONING. + */ + public static final int SKILL_ATTACK = 0; + + /** + * The Constant SKILL_DEFENCE. + */ + public static final int SKILL_DEFENCE = 1; + + /** + * The Constant SKILL_STRENGTH. + */ + public static final int SKILL_STRENGTH = 2; + + /** + * The Constant SKILL_HITPOINTS. + */ + public static final int SKILL_HITPOINTS = 3; + + /** + * The Constant SKILL_RANGE. + */ + public static final int SKILL_RANGE = 4; + + /** + * The Constant SKILL_PRAYER. + */ + public static final int SKILL_PRAYER = 5; + + /** + * The Constant SKILL_MAGIC. + */ + public static final int SKILL_MAGIC = 6; + + /** + * The Constant SKILL_COOKING. + */ + public static final int SKILL_COOKING = 7; + + /** + * The Constant SKILL_WOODCUTTING. + */ + public static final int SKILL_WOODCUTTING = 8; + + /** + * The Constant SKILL_FLETCHING. + */ + public static final int SKILL_FLETCHING = 9; + + /** + * The Constant SKILL_FISHING. + */ + public static final int SKILL_FISHING = 10; + + /** + * The Constant SKILL_FIREMAKING. + */ + public static final int SKILL_FIREMAKING = 11; + + /** + * The Constant SKILL_CRAFTING. + */ + public static final int SKILL_CRAFTING = 12; + + /** + * The Constant SKILL_SMITHING. + */ + public static final int SKILL_SMITHING = 13; + + /** + * The Constant SKILL_MINING. + */ + public static final int SKILL_MINING = 14; + + /** + * The Constant SKILL_HERBLORE. + */ + public static final int SKILL_HERBLORE = 15; + + /** + * The Constant SKILL_AGILITY. + */ + public static final int SKILL_AGILITY = 16; + + /** + * The Constant SKILL_THIEVING. + */ + public static final int SKILL_THIEVING = 17; + + /** + * The Constant SKILL_SLAYER. + */ + public static final int SKILL_SLAYER = 18; + + /** + * The Constant SKILL_FARMING. + */ + public static final int SKILL_FARMING = 19; + + /** + * The Constant SKILL_RUNECRAFTING. + */ + public static final int SKILL_RUNECRAFTING = 20; + + /** + * The Constant SKILL_CONSTRUCTION. + */ + public static final int SKILL_CONSTRUCTION = 21; + + /** + * The Constant SKILL_HUNTER. + */ + public static final int SKILL_HUNTER = 22; + + /** + * The Constant SKILL_SUMMONING. + */ + public static final int SKILL_SUMMONING = 23; + + /** + * The Constant SKILL_NAMES. + */ + public static final String[] SKILL_NAMES = { "Attack", "Defence", "Strength", "Hitpoints", "Range", "Prayer", "Magic", "Cooking", "Woodcutting", "Fletching", "Fishing", "Firemaking", "Crafting", "Smithing", "Mining", "Herblore", "Agility", "Thieving", "Slayer", "Farming", "Runecrafting", "Construction", "Hunter", "Summoning" }; +} diff --git a/src/osiris/game/model/Viewable.java b/src/osiris/game/model/Viewable.java new file mode 100644 index 0000000..36fd6cd --- /dev/null +++ b/src/osiris/game/model/Viewable.java @@ -0,0 +1,79 @@ +package osiris.game.model; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.model.ground.GroundItem; + +// TODO: Auto-generated Javadoc +/** + * The Class Viewable. + * + * @author Boomer + */ +public class Viewable { + + /** The position. */ + private Position position = new Position(); + + /** + * Checks if is in sight. + * + * @param other + * the other + * @return true, if is in sight + */ + public boolean isInSight(Viewable other) { + int radius = 15; + if (this instanceof GroundItem || other instanceof GroundItem || this instanceof WorldObject || other instanceof WorldObject) + radius = 32; + return isInSight(other.getPosition(), radius); + } + + /** + * Checks if is in sight. + * + * @param position + * the position + * @param radius + * the radius + * @return true, if is in sight + */ + public boolean isInSight(Position position, int radius) { + return position.inSight(this.getPosition(), radius); + } + + /** + * Gets the position. + * + * @return the position + */ + public Position getPosition() { + return position; + } + + /** + * Sets the position. + * + * @param position + * the new position + */ + public void setPosition(Position position) { + this.position = position; + } +} diff --git a/src/osiris/game/model/WorldObject.java b/src/osiris/game/model/WorldObject.java new file mode 100644 index 0000000..c7a54eb --- /dev/null +++ b/src/osiris/game/model/WorldObject.java @@ -0,0 +1,116 @@ +package osiris.game.model; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * The Class WorldObject. + * + * @author Blake Beaupain + */ +public class WorldObject extends Viewable { + + /** The object id. */ + private int objectID; + + /** The object face. */ + private final int objectFace; + + /** The object type. */ + private final int objectType; + + /** + * Instantiates a new world object. + * + * @param objectID + * the object id + * @param objectFace + * the object face + * @param objectType + * the object type + * @param objectPosition + * the object position + */ + public WorldObject(int objectID, int objectFace, int objectType, Position objectPosition) { + this.objectID = objectID; + this.objectFace = objectFace; + this.objectType = objectType; + setPosition(objectPosition); + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "WorldObject[id=" + objectID + ", face=" + objectFace + ", type=" + objectType + ", position=" + getPosition(); + } + + /** + * Gets the object id. + * + * @return the object id + */ + public int getObjectID() { + return objectID; + } + + /** + * Gets the object face. + * + * @return the object face + */ + public int getObjectFace() { + return objectFace; + } + + /** + * Gets the object type. + * + * @return the object type + */ + public int getObjectType() { + return objectType; + } + + /** + * Gets the object position. + * + * @return the object position + */ + public Position getObjectPosition() { + return this.getPosition(); + } + + /** + * Sets the object id. + * + * @param id + * the new object id + */ + public void setObjectId(int id) { + this.objectID = id; + } + + public WorldObject clone() { + return new WorldObject(objectID, objectFace, objectType, getPosition()); + } + +} \ No newline at end of file diff --git a/src/osiris/game/model/WorldObjects.java b/src/osiris/game/model/WorldObjects.java new file mode 100644 index 0000000..dbdc48f --- /dev/null +++ b/src/osiris/game/model/WorldObjects.java @@ -0,0 +1,105 @@ +package osiris.game.model; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.util.LinkedList; +import java.util.List; + +import osiris.Main; + +// TODO: Auto-generated Javadoc +/** + * The Class WorldObjects. + * + * @author Blake Beaupain + */ +public class WorldObjects { + + /** The objects. */ + private static List objects = new LinkedList(); + + /** + * Gets the objects. + * + * @return the objects + */ + public static List getObjects() { + return objects; + } + + /** + * Gets the object. + * + * @param position + * the position + * @return the object + */ + public static WorldObject getObject(Position position) { + for (WorldObject object : getObjects()) + if (object.getObjectPosition().equals(position)) + return object; + return null; + } + + /** + * Refresh. + * + * @param player + * the player + */ + public static void refresh(Player player) { + for (WorldObject object : WorldObjects.getObjects()) { + if (object.isInSight(player)) + player.getEventWriter().setObject(object); + } + } + + /** + * Removes the object. + * + * @param obj + * the obj + */ + public static void removeObject(WorldObject obj) { + if (obj == null) + return; + obj = obj.clone(); + getObjects().remove(obj); + obj.setObjectId(6951); + for (Player player : Main.getPlayers()) + if (player.isInSight(obj)) + player.getEventWriter().setObject(obj); + } + + /** + * Adds the object. + * + * @param obj + * the obj + */ + public static void addObject(WorldObject obj) { + if (obj == null) + return; + for (Player player : Main.getPlayers()) + if (player.isInSight(obj)) + player.getEventWriter().setObject(obj); + getObjects().add(obj); + } + +} diff --git a/src/osiris/game/model/XValue.java b/src/osiris/game/model/XValue.java new file mode 100644 index 0000000..cf677ee --- /dev/null +++ b/src/osiris/game/model/XValue.java @@ -0,0 +1,80 @@ +package osiris.game.model; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.action.Action; +import osiris.game.action.impl.BankAction; +import osiris.game.action.impl.TradeAction; + +/** + * @author Boomer + * + */ +public class XValue { + + private Player player; + private Action action; + private int itemSlot; + private boolean toInventory; + + public XValue(Player player, Action action) { + this.player = player; + this.action = action; + this.itemSlot = -1; + this.toInventory = false; + player.setXValue(this); + player.getEventWriter().runScript(108, new Object[] { "Enter amount." }, "s"); + } + + public XValue(Player player, Action action, int itemSlot, boolean toInventory) { + this(player, action); + this.itemSlot = itemSlot; + this.toInventory = toInventory; + } + + public boolean execute(int value) { + if (action == null || player.getCurrentAction() == null || !player.getCurrentAction().equals(action) || value == 0) + return cancel(false); + else { + if (action instanceof TradeAction) { + if (itemSlot == -1) + return cancel(false); + if (toInventory) + ((TradeAction) action).getTrade().removeTradeItem(player, itemSlot, value); + else + ((TradeAction) action).getTrade().addTradeItem(player, itemSlot, value); + } else if (action instanceof BankAction) { + if (itemSlot == -1) + return cancel(false); + if (toInventory) + action.getPlayer().withdrawItem(itemSlot, value); + else + action.getPlayer().depositItem(itemSlot, value); + } + cancel(true); + } + return true; + } + + public boolean cancel(boolean finished) { + player.getEventWriter().sendCloseChatboxInterface(); + player.setXValue(null); + return finished; + } +} diff --git a/src/osiris/game/model/combat/Attack.java b/src/osiris/game/model/combat/Attack.java new file mode 100644 index 0000000..1acb997 --- /dev/null +++ b/src/osiris/game/model/combat/Attack.java @@ -0,0 +1,323 @@ +package osiris.game.model.combat; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.lang.reflect.InvocationTargetException; +import java.util.HashMap; +import java.util.Random; + +import osiris.ServerEngine; +import osiris.game.model.Character; +import osiris.game.model.Player; +import osiris.game.model.def.ItemDef; +import osiris.game.model.effect.CombatEffect; +import osiris.game.model.effect.PoisonEffect; +import osiris.game.model.item.Item; +import osiris.game.update.block.AnimationBlock; +import osiris.game.update.block.GraphicsBlock; +import osiris.util.Settings; +import osiris.util.Utilities; + +// TODO: Auto-generated Javadoc +/** + * The Class Attack. + * + * @author Boomer + */ +public class Attack { + + /** The hit type. */ + private HitType hitType; + + /** The delay. */ + private int delay; + + /** The hit graphic. */ + private int[] animation, attackGraphic, hitGraphic; + // projectile id, speed, height, delay + /** The projectiles. */ + private int[][] projectiles; + // max hit, delay, drop arrow (if there is a drop arrow) + /** The hits. */ + private int[][] hits; + + /** The chance. */ + private int chance; + // effect, duration, chance, + /** The effects. */ + private HashMap effects; + + /** + * Instantiates a new attack. + */ + public Attack() { + this.delay = 3; + this.animation = new int[] { 386, 451, 451, 386 }; + this.hitType = HitType.MELEE; + this.attackGraphic = null; + this.hitGraphic = null; + this.projectiles = null; + this.hits = new int[][] { { 0, 0 } }; + this.chance = 100; + this.effects = new HashMap(); + } + + /** + * Adds the effect. + * + * @param hitIndex + * the hit index + * @param chance + * the chance + * @param clazz + * the clazz + * @param cooldown + * the cooldown + * @param params + * the params + * @return the attack + */ + public Attack addEffect(int hitIndex, int chance, Class clazz, int cooldown, Object... params) { + effects.put(hitIndex, new Object[] { chance, cooldown, clazz, params }); + return this; + } + + /** + * Execute. + * + * @param character + * the character + * @param victim + * the victim + * @param random + * the random + * @return the int + */ + public int execute(Character character, Character victim, boolean random) { + int anim = -1; + int attackDelay = delay; + if (animation != null) { + if (character instanceof Player) { + int attackStyle = ((Player) character).getAttackStyle(); + if (animation.length > attackStyle) + anim = animation[attackStyle]; + else + anim = animation[0]; + } else + anim = animation[new Random().nextInt(animation.length)]; + } + if (attackGraphic != null) { + character.addPriorityUpdateBlock(new GraphicsBlock(character, attackGraphic[0], attackGraphic[1])); + } + if (anim != -1) + character.addPriorityUpdateBlock(new AnimationBlock(character, anim, 0)); + if (projectiles != null) + for (int p = 0; p < projectiles.length; p++) { + int[] projectile = projectiles[p]; + Projectile proj = new Projectile(projectile[0], character.getPosition(), victim.getPosition(), 50, projectile[1], new int[] { projectile[2], 31 }, victim, projectile[3]); + character.getCombatManager().getProjectiles().add(proj); + } + for (int h = 0; h < hits.length; h++) { + int[] hitInfo = hits[h]; + int randomHit = hitInfo[0]; + if (random) + randomHit = Utilities.random(randomHit); + Hit hit = new Hit(character, victim, randomHit, hitInfo[1], hitType); + Object[] effect = effects.get(h); + if (effect != null) { + if (new Random().nextInt(100) <= (Integer) effect[0]) { + // noinspection unchecked + hit.setEffects(CombatEffect.create(character, (Integer) effect[1], (Class) effect[2], (Object[]) effect[3])); + } + } + if (hitGraphic != null) + hit.setGraphic(hitGraphic[0], hitGraphic[1]); + if (hitInfo.length > 2) + if (hitInfo[2] != -1) + hit.setDropArrow(Item.create(hitInfo[2])); + double poisonValue = -1; + if (character instanceof Player) { + String wepName = null; + Item weapon = ((Player) character).getEquipment().getItem(Settings.SLOT_WEAPON); + if (weapon != null) + wepName = ItemDef.forId(weapon.getId()).getName().toLowerCase(); + if (wepName != null) { + if (wepName.contains("(p") || wepName.contains("(s") || wepName.contains("(k")) { + if (wepName.contains("++") || wepName.contains("(s")) + poisonValue = 6.0; + else if (wepName.contains("+")) + poisonValue = 4.0; + else + poisonValue = 2.0; + } + } + } + if (poisonValue != -1) + hit.setEffects(new PoisonEffect(-1, character, poisonValue)); + ServerEngine.getHitQueue().add(hit); + } + if (hitType == HitType.RANGED) { + boolean rapid = false; + if (character instanceof Player) + rapid = ((Player) character).getAttackStyle() == 1; + if (rapid) + attackDelay--; + } + return attackDelay; + } + + /** + * Gets the hit type. + * + * @return the hit type + */ + public HitType getHitType() { + return hitType; + } + + /** + * Gets the attack delay. + * + * @return the attack delay + */ + public int getAttackDelay() { + return delay; + } + + /** + * Sets the hit type. + * + * @param hitType + * the hit type + * @return the attack + */ + public Attack setHitType(HitType hitType) { + this.hitType = hitType; + return this; + } + + /** + * Sets the delay. + * + * @param delay + * the delay + * @return the attack + */ + public Attack setDelay(int delay) { + this.delay = delay; + return this; + } + + /** + * Sets the animation. + * + * @param animation + * the animation + * @return the attack + */ + public Attack setAnimation(int[] animation) { + this.animation = animation; + return this; + } + + /** + * Sets the attack gfx. + * + * @param graphic + * the graphic + * @return the attack + */ + public Attack setAttackGfx(int[] graphic) { + this.attackGraphic = graphic; + return this; + } + + /** + * Sets the hit gfx. + * + * @param graphic + * the graphic + * @return the attack + */ + public Attack setHitGfx(int[] graphic) { + this.hitGraphic = graphic; + return this; + } + + /** + * Sets the chance. + * + * @param chance + * the chance + * @return the attack + */ + public Attack setChance(int chance) { + this.chance = chance; + return this; + } + + /** + * Sets the projectiles. + * + * @param projectiles + * the projectiles + * @return the attack + */ + public Attack setProjectiles(int[][] projectiles) { + this.projectiles = projectiles; + return this; + } + + /** + * Sets the hits. + * + * @param hits + * the hits + * @return the attack + */ + public Attack setHits(int[][] hits) { + this.hits = hits; + return this; + } + + /** + * Creates the. + * + * @param className + * the class name + * @return the attack + */ + public static Attack create(String className) { + try { + return (Attack) Class.forName("osiris.game.model.combat.impl." + className).getConstructor().newInstance(); + } catch (InstantiationException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + return null; + } + +} diff --git a/src/osiris/game/model/combat/CombatDefaults.java b/src/osiris/game/model/combat/CombatDefaults.java new file mode 100644 index 0000000..c8b5f0b --- /dev/null +++ b/src/osiris/game/model/combat/CombatDefaults.java @@ -0,0 +1,55 @@ +package osiris.game.model.combat; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +// TODO: Auto-generated Javadoc +/** + * The Class CombatDefaults. + * + * @author Boomer + * + */ +public final class CombatDefaults { + + /** The Constant DEFAULT_SPRITES. */ + public static final int[] DEFAULT_ANIMATIONS = { 386, 451, 451, 386 }, DEFAULT_SPRITES = { 0x328, 0x333, 0x338 }; + + /** The Constant DEFAULT_TAB. */ + public static final int DEFAULT_TAB = 82; + + /** The Constant DEFAULT_BLOCK. */ + public static final int DEFAULT_BLOCK = 404; + + /** The Constant SPELL_SKILLS. */ + public static final int[][] DEFAULT_SKILLS = new int[][] { { 0 }, { 2 }, { 0, 1, 2 }, { 1 } }; + + /** The Constant SWORD_SKILLS. */ + public static final int[][] SWORD_SKILLS = new int[][] { { 0 }, { 2 }, { 2 }, { 1 } }; + + /** The Constant RANGE_SKILLS. */ + public static final int[][] RANGE_SKILLS = new int[][] { { 4 }, { 4 }, { 4, 1 } }; + + /** + * Prevents initilization. + */ + private CombatDefaults() { + // Nothing to do here... + } + +} diff --git a/src/osiris/game/model/combat/CombatManager.java b/src/osiris/game/model/combat/CombatManager.java new file mode 100644 index 0000000..a8683e7 --- /dev/null +++ b/src/osiris/game/model/combat/CombatManager.java @@ -0,0 +1,502 @@ +package osiris.game.model.combat; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; + +import osiris.ServerEngine; +import osiris.data.NpcDropLoader; +import osiris.game.action.impl.CombatAction; +import osiris.game.model.Character; +import osiris.game.model.Death; +import osiris.game.model.Npc; +import osiris.game.model.NpcDrop; +import osiris.game.model.NpcDropContainer; +import osiris.game.model.Player; +import osiris.game.model.combat.impl.MagicAttack; +import osiris.game.model.combat.impl.PlayerAttack; +import osiris.game.model.combat.missile.Arrow; +import osiris.game.model.combat.missile.Knife; +import osiris.game.model.combat.missile.RangedAttack; +import osiris.game.model.effect.Effect; +import osiris.game.model.effect.InCombatEffect; +import osiris.game.model.item.Item; +import osiris.game.model.magic.MagicManager; +import osiris.game.model.magic.Spell; +import osiris.game.model.magic.SpellBook; +import osiris.game.update.block.PrimaryHitBlock; +import osiris.game.update.block.SecondaryHitBlock; +import osiris.util.Settings; +import osiris.util.StopWatch; +import osiris.util.Utilities; + +// TODO: Auto-generated Javadoc + +/** + * The Class CombatManager. + * + * @author Boomer + * + */ +public class CombatManager { + + /** + * The character. + */ + private final Character character; + + /** + * The attack timer. + */ + private StopWatch attackTimer; + + /** + * The attack delay. + */ + private int attackDelay; + + /** + * The combat enabled. + */ + private boolean combatEnabled; + + /** The combat tempSpell. */ + private Spell tempSpell, autoSpell; + + /** + * The updating hits. + */ + private ArrayList updatingHits; + + /** + * The hits story. Its punny. Get it? + */ + private ArrayList hitsStory; + + /** The projectiles. */ + private ArrayList projectiles; + + /** The combat action. */ + private CombatAction combatAction; + + /** The equipped weapon. */ + private EquippedWeapon equippedWeapon; + + /** The Constant HITS_STORY_DECAY. */ + public static final int HITS_STORY_DECAY = 150; + + /** + * Instantiates a new combat manager. + * + * @param character + * the character + */ + public CombatManager(Character character) { + this.character = character; + this.updatingHits = new ArrayList(); + this.hitsStory = new ArrayList(); + this.attackDelay = 0; + this.attackTimer = new StopWatch(); + this.projectiles = new ArrayList(); + this.tempSpell = null; + this.autoSpell = null; + this.equippedWeapon = null; + } + + /** + * Cycle. + */ + public void cycle() { + if (character == null) + return; + + if (combatAction != null) { + if (character.getInteractingCharacter() != null) { + Character victim = character.getInteractingCharacter(); + Attack attack = new Attack(); + if (character instanceof Player) { + Spell spell = autoSpell; + if (tempSpell != null) + spell = tempSpell; + if (spell != null) + attack = new MagicAttack(spell); + else + attack = new PlayerAttack(((Player) character).getEquippedAttack()); + } else if (character instanceof Npc) { + Npc npc = (Npc) character; + attack = npc.getCombatDef().getAttack(); + } + HitType hitType = attack.getHitType(); + int attackRange = CombatUtilities.calculateCombatDistance(hitType); + int distanceFromVictim = Utilities.getDistance(character.getPosition(), victim.getPosition()); + if (distanceFromVictim <= attackRange) { + character.getMovementQueue().reset(); + if (attackTimer.elapsed() >= attackDelay && character.getCurrentHp() > 0) { + if (combatEnabled) { + if (hitType == HitType.MAGIC || hitType == HitType.RANGED) { + character.getMovementQueue().reset(); + } + int attackResult = executeAttack(attack); + if (tempSpell != null) { + combatEnabled = false; + } + if (attackResult == -1) { + cancel(); + } else + attackDelay = attackResult; + attackTimer.reset(); + } else { + cancel(); + } + } + } else { + if (character.getMovementQueue().isEmpty()) { + cancel(); + } + } + } else { + cancel(); + } + } + + for (Iterator shotProjectiles = projectiles.iterator(); shotProjectiles.hasNext();) { + Projectile projectile = shotProjectiles.next(); + if (projectile.shouldFire()) { + projectile.show(character); + shotProjectiles.remove(); + } + } + + boolean primaryTaken = false; + for (Iterator hits = updatingHits.iterator(); hits.hasNext();) { + HitResult hit = hits.next(); + if (!primaryTaken) { + character.addUpdateBlock(new PrimaryHitBlock(character, hit.getDamage(), hit.getHitType())); + primaryTaken = true; + if (hit.getDamage() > 0 && hit.getAttackerUId() != -1) + hitsStory.add(hit); + hits.remove(); + } else { + character.addUpdateBlock(new SecondaryHitBlock(character, hit.getDamage(), hit.getHitType())); + if (hit.getDamage() > 0 && hit.getAttackerUId() != -1) + hitsStory.add(hit); + hits.remove(); + break; + } + } + if (character.getCurrentHp() == 0) + executeDeath(); + for (Iterator pastHits = hitsStory.iterator(); pastHits.hasNext();) { + if (pastHits.next().getDecay() >= HITS_STORY_DECAY) + pastHits.remove(); + } + } + + /** + * Can attack. + * + * @param victim + * the victim + * @return true, if successful + */ + public boolean canAttack(Character victim) { + if (victim == null || victim.isDying() || victim.getCurrentHp() == 0 || character == null || character.getCurrentHp() == 0 || character.isDying() || !character.isInSight(victim)) { + return false; + } + return !victim.isDying() && CombatUtilities.checkWilderness(character, victim); + } + + /** + * Attack character. + * + * @param victim + * the victim + * @param spellId + * the spell id + * @return true, if successful + */ + public boolean attackCharacter(Character victim, int spellId) { + if (victim.getMaxHp() == 0) + return false; + if (spellId != -1) + this.tempSpell = MagicManager.getSpell(spellId, character.getSpellBook()); + else + tempSpell = null; + if (canAttack(victim)) { + this.character.setInteractingCharacter(victim); + combatEnabled = true; + return true; + } + return false; + } + + /** + * Reset attack vars. + */ + public void resetAttackVars() { + character.setInteractingCharacter(null); + combatEnabled = false; + this.combatAction = null; + this.setAttackType(null); + this.tempSpell = null; + } + + /** + * Execute attack. + * + * @param attack + * the attack + * @return the int + */ + public int executeAttack(Attack attack) { + Character victim = character.getInteractingCharacter(); + if (!canAttack(victim)) + return -1; + character.faceCharacter(victim); + return attack.execute(character, victim, true); + } + + /** + * Execute death. + */ + public void executeDeath() { + if (character.isDying()) + return; + ArrayList items = new ArrayList(); + Character killer = CombatUtilities.calculateKiller(hitsStory); + if (character instanceof Player) { + items.addAll(Arrays.asList(((Player) character).getInventory().getItems())); + items.addAll(Arrays.asList(((Player) character).getEquipment().getItems())); + } else if (character instanceof Npc) { + ArrayList dropContainers = NpcDropLoader.getContainers(((Npc) character).getId()); + for (NpcDropContainer dropContainer : dropContainers) + for (NpcDrop drop : dropContainer.produceRandomDrops(killer)) + items.add(new Item(drop.getItemId(), drop.getAmount())); + } + Death death = new Death(character, killer, items); + if (character instanceof Npc) + death.setDuration(30); + ServerEngine.getDeathManager().getDeaths().add(death); + } + + /** + * Gets the updating hits. + * + * @return the updating hits + */ + public ArrayList getUpdatingHits() { + return updatingHits; + } + + /** + * Combat enabled. + * + * @return true, if successful + */ + public boolean combatEnabled() { + return combatEnabled; + } + + /** + * Update hits. + */ + public static void updateHits() { + if (ServerEngine.getHitQueue() == null || ServerEngine.getHitQueue().size() == 0) + return; + for (Iterator hits = ServerEngine.getHitQueue().iterator(); hits.hasNext();) { + Hit hit = hits.next(); + if (hit.getVictim().isDying()) + hits.remove(); + else if (hit.getTimer().elapsed() < hit.getDelay()) { + continue; + } else if (hit.getVictim() != null) { + if (hit.execute()) { + if (hit.getHit() != -1) + hit.getVictim().getCombatManager().getUpdatingHits().add(new HitResult(hit.getHit(), hit.getAttackType(), ((hit.getAttacker() != null && hit.getAttacker() instanceof Player) ? ((Player) hit.getAttacker()).getUniqueId() : -1))); + } + hits.remove(); + } + } + } + + /** + * Sets the combat action. + * + * @param action + * the new combat action + */ + public void setCombatAction(CombatAction action) { + this.combatAction = action; + action.run(); + } + + /** + * Sets the attack type. + * + * @param equippedWeapon + * the new attack type + */ + public void setAttackType(EquippedWeapon equippedWeapon) { + this.equippedWeapon = equippedWeapon; + } + + /** + * Gets the attack type. + * + * @return the attack type + */ + public EquippedWeapon getAttackType() { + return equippedWeapon; + } + + /** + * Gets the projectiles. + * + * @return the projectiles + */ + public ArrayList getProjectiles() { + return projectiles; + } + + /** + * Gets the temp spell. + * + * @return the temp spell + */ + public Spell getTempSpell() { + return tempSpell; + } + + /** + * Gets the auto spell. + * + * @return the auto spell + */ + public Spell getAutoSpell() { + return autoSpell; + } + + /** + * Sets the auto spell. + * + * @param spellId + * the new auto spell + */ + public void setAutoSpell(int spellId) { + setAutoSpell(spellId, character.getSpellBook(), false, -1); + } + + /** + * Sets the auto spell. + * + * @param spellId + * the spell id + * @param spellBook + * the spell book + * @param defence + * the defence + * @param spellSprite + * the spell sprite + */ + public void setAutoSpell(int spellId, SpellBook spellBook, boolean defence, int spellSprite) { + if (spellId == -1) + this.autoSpell = null; + else + this.autoSpell = MagicManager.getSpell(spellId, spellBook); + if (character instanceof Player) { + Player player = (Player) character; + if (spellId == -1) { + player.getEventWriter().sendConfig2(439, 0); + player.getEventWriter().sendInterfaceConfig(90, 83, false); + player.getEventWriter().sendInterfaceConfig(90, 183, false); + player.refreshWeaponTab(); + } else if (spellSprite != -1) { + player.getEventWriter().sendInterfaceConfig(90, defence ? 183 : 83, true); + player.getEventWriter().sendInterfaceConfig(90, spellSprite, false); + player.getEventWriter().sendConfig2(defence ? 439 : 43, defence ? -5 : 3); + } + } + } + + /** + * Cancel. + */ + public void cancel() { + if (character instanceof Npc) { + if (!((Npc) character).getWalkablePositions().contains(character.getPosition())) + character.getMovementQueue().addStep(((Npc) character).getDefaultPosition()); + } + if (character.getCurrentAction() != null && character.getCurrentAction() instanceof CombatAction) { + character.getCurrentAction().cancel(); + } + } + + /** + * Gets the ranged attack. + * + * @return the ranged attack + */ + public RangedAttack getRangedAttack() { + EquippedWeapon.WeaponType wepType = null; + if (this.getAttackType() != null) + wepType = this.getAttackType().getWepType(); + else { + if (character instanceof Player) + wepType = ((Player) character).getEquippedAttack().getWepType(); + } + if (wepType == null) + return null; + else if (wepType == EquippedWeapon.WeaponType.SHORTBOW) + return Arrow.getAmmo(character, Settings.SLOT_ARROWS); + else if (wepType == EquippedWeapon.WeaponType.LONGBOW) + return Arrow.getAmmo(character, Settings.SLOT_ARROWS); + else if (wepType == EquippedWeapon.WeaponType.THROWING_KNIFE) + return Knife.getAmmo(character, Settings.SLOT_WEAPON); + return null; + } + + /** + * Adjust attack delay. + * + * @param adjustment + * the adjustment + */ + public void adjustAttackDelay(int adjustment) { + if (adjustment < 0) { + attackDelay += adjustment; + if (attackDelay < 0) + attackDelay = 0; + } else + attackDelay += adjustment; + } + + /** + * Gets the combat effect. + * + * @return the combat effect + */ + public InCombatEffect getCombatEffect() { + for (Effect effect : character.getEffects()) + if (effect instanceof InCombatEffect) + return (InCombatEffect) effect; + return null; + } +} diff --git a/src/osiris/game/model/combat/CombatUtilities.java b/src/osiris/game/model/combat/CombatUtilities.java new file mode 100644 index 0000000..c496ebb --- /dev/null +++ b/src/osiris/game/model/combat/CombatUtilities.java @@ -0,0 +1,214 @@ +package osiris.game.model.combat; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import osiris.Main; +import osiris.game.model.Character; +import osiris.game.model.Player; +import osiris.game.model.Position; +import osiris.game.model.Skills; +import osiris.game.model.combat.missile.RangedAttack; +import osiris.game.model.effect.InCombatEffect; +import osiris.game.model.effect.PrayerEffect; +import osiris.game.model.skills.Prayer; + +// TODO: Auto-generated Javadoc +/** + * The Class CombatUtilities. + * + * @author Boomer + * + */ +public class CombatUtilities { + + /** + * Calculate hit damage. + * + * @param character + * the character + * @param attack + * the attack + * @param ranged + * the ranged + * @return the int + */ + public static int calculateMaxDamage(Character character, EquippedWeapon attack, RangedAttack ranged) { + if (character instanceof Player) { + Player player = (Player) character; + if (attack.getHitType() == HitType.RANGED) { + double prayerBonus = 1; + PrayerEffect prayer = player.getPrayers().get(Prayer.Type.RANGE); + if (prayer != null) + prayerBonus += .05 * prayer.getPrayer().getStage(); + int rangedLevel = player.getSkills().currentLevel(Skills.SKILL_RANGE); + double effectiveStrength = Math.floor(rangedLevel * prayerBonus) + (player.getAttackStyle() == 0 ? 3 : 0); + int rangedStrength = 0; + if (ranged != null) + rangedStrength = ranged.getRangeBonus(); + double baseDamage = 5 + (((effectiveStrength + 8) * (rangedStrength + 64)) / 64); + double bonus = 1; + baseDamage *= bonus; + int max = (int) Math.floor(baseDamage) / 10; + return max; + + } else if (attack.getHitType() == HitType.MELEE) { + int strengthLevel = ((Player) character).getSkills().currentLevel(Skills.SKILL_STRENGTH); + int strengthBonus = ((Player) character).getBonuses().get(10); + double prayerBonus = 1; + PrayerEffect prayer = player.getPrayers().get(Prayer.Type.RANGE); + if (prayer != null) + prayerBonus += .05 * prayer.getPrayer().getStage(); + else if ((prayer = player.getPrayers().get(Prayer.Type.BADASS)) != null) + prayerBonus += (prayer.getPrayer() == Prayer.CHIVALRY ? .18 : .23); + strengthLevel *= prayerBonus; + int attackStyle = ((Player) character).getAttackStyle(); + for (int i : attack.getSkills()[attackStyle]) + if (i == Skills.SKILL_STRENGTH) { + strengthLevel += 3; + break; + } + double multiplier = 0.10 + (strengthBonus * 0.00175); + double maxHit = 1.05 + (strengthLevel * multiplier); + double finalMultiplier = 1; + maxHit *= finalMultiplier; + return (int) maxHit; + } + } + return 0; + } + + /** + * Single combat. + * + * @param position + * the position + * @return true, if successful + */ + public static boolean singleCombat(Position position) { + return true; + } + + /** + * Calculate killer. + * + * @param hitsStory + * the hits story + * @return the character + */ + public static Character calculateKiller(ArrayList hitsStory) { + Map sortedHits = new HashMap(); + for (HitResult hit : hitsStory) { + if (sortedHits.containsKey(hit.getAttackerUId())) + sortedHits.put(hit.getAttackerUId(), sortedHits.get(hit.getAttackerUId()) + hit.getDamage()); + else + sortedHits.put(hit.getAttackerUId(), hit.getDamage()); + } + int currentLeader = -1; + int currentDamage = 0; + for (Map.Entry e : sortedHits.entrySet()) { + if (e.getValue() > currentDamage) { + currentLeader = e.getKey(); + currentDamage = e.getValue(); + } + } + if (currentLeader == -1) + return null; + else + return Main.findPlayer(currentLeader); + } + + /** + * Calculate combat distance. + * + * @param type + * the type + * @return the int + */ + public static int calculateCombatDistance(HitType type) { + switch (type) { + case MELEE: + return 1; + case RANGED: + return 8; + case MAGIC: + case OTHER: + return 12; + } + return -1; + } + + /** + * Check wilderness. + * + * @param attacker + * the attacker + * @param victim + * the victim + * @return true, if successful + */ + public static boolean checkWilderness(Character attacker, Character victim) { + InCombatEffect attackerInCombat = attacker.getCombatManager().getCombatEffect(); + InCombatEffect victimInCombat = victim.getCombatManager().getCombatEffect(); + InCombatEffect toAdd = new InCombatEffect(attacker); + if (attackerInCombat == null && victimInCombat == null) { + victim.addEffect(toAdd); + return true; + } + boolean attackerSingle = singleCombat(attacker.getPosition()); + boolean victimSingle = singleCombat(victim.getPosition()); + if (attackerSingle && attackerInCombat != null && attackerInCombat.getAttacker() != null && !attackerInCombat.getAttacker().equals(victim)) { + if (attackerInCombat.getAttacker().isDying()) + attacker.getEffects().remove(attackerInCombat); + else { + trySendMessage(attacker, "You are already in combat!"); + return false; + } + } + if (victimSingle && victimInCombat != null && victimInCombat.getAttacker() != null && !victimInCombat.getAttacker().equals(attacker)) { + if (victimInCombat.getAttacker().isDying()) + victim.getEffects().remove(victimInCombat); + else { + trySendMessage(attacker, "You cannot attack that " + victim.getClass().getSimpleName().toLowerCase()); + return false; + } + } + if (victimInCombat != null) + victimInCombat.getTimer().reset(); + else + victim.addEffect(toAdd); + return true; + } + + /** + * Try send message. + * + * @param character + * the character + * @param message + * the message + */ + public static void trySendMessage(Character character, String message) { + if (character instanceof Player) + ((Player) character).getEventWriter().sendMessage(message); + } +} \ No newline at end of file diff --git a/src/osiris/game/model/combat/EquippedWeapon.java b/src/osiris/game/model/combat/EquippedWeapon.java new file mode 100644 index 0000000..071f67a --- /dev/null +++ b/src/osiris/game/model/combat/EquippedWeapon.java @@ -0,0 +1,406 @@ +package osiris.game.model.combat; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import static osiris.game.model.combat.CombatDefaults.DEFAULT_ANIMATIONS; +import static osiris.game.model.combat.CombatDefaults.DEFAULT_BLOCK; +import static osiris.game.model.combat.CombatDefaults.DEFAULT_SKILLS; +import static osiris.game.model.combat.CombatDefaults.DEFAULT_SPRITES; +import static osiris.game.model.combat.CombatDefaults.DEFAULT_TAB; +import static osiris.game.model.combat.CombatDefaults.RANGE_SKILLS; +import static osiris.game.model.combat.CombatDefaults.SWORD_SKILLS; + +// TODO: Auto-generated Javadoc +/** + * The Class EquippedWeapon. + * + * @author Boomer + */ +public class EquippedWeapon { + + // stand, standturn, walk, turn180, turn90CW, turn90CCW, run + /** + * Gets the fists. + * + * @return the fists + */ + public static EquippedWeapon getFists() { + return new EquippedWeapon(WeaponType.FISTS, HitType.MELEE, 4, new int[] { 92, 10, 24 }, new int[][] { { 0 }, { 2 }, { 1 } }, array(DEFAULT_SPRITES), new int[] { 422, 423, 422 }, 424); + } + + /** + * Gets the sword. + * + * @return the sword + */ + public static EquippedWeapon getSword() { + return new EquippedWeapon(WeaponType.SWORD, HitType.MELEE, 4, new int[] { DEFAULT_TAB, 12, 26 }, SWORD_SKILLS, DEFAULT_SPRITES, array(DEFAULT_ANIMATIONS), 397); + } + + /** + * Gets the longsword. + * + * @return the longsword + */ + public static EquippedWeapon getLongsword() { + return new EquippedWeapon(WeaponType.LONGSWORD, HitType.MELEE, 5, new int[] { DEFAULT_TAB, 12, 26 }, SWORD_SKILLS, DEFAULT_SPRITES, array(DEFAULT_ANIMATIONS), 397); + } + + /** + * Gets the 2h sword. + * + * @return the 2h sword + */ + public static EquippedWeapon get2hSword() { + return new EquippedWeapon(WeaponType.TWO_HANDED_SWORD, HitType.MELEE, 7, new int[] { 81, 12, 26 }, DEFAULT_SKILLS, new int[] { 7047, 7046, 7039 }, new int[] { 7048, 7041, 7041, 7049 }, 7050); + } + + /** + * Gets the mace. + * + * @return the mace + */ + public static EquippedWeapon getMace() { + return new EquippedWeapon(WeaponType.MACE, HitType.MELEE, 5, new int[] { 88, 12, 26 }, DEFAULT_SKILLS, DEFAULT_SPRITES, array(DEFAULT_ANIMATIONS), DEFAULT_BLOCK); + } + + /** + * Gets the scimitar. + * + * @return the scimitar + */ + public static EquippedWeapon getScimitar() { + return new EquippedWeapon(WeaponType.SCIMITAR, HitType.MELEE, 4, new int[] { DEFAULT_TAB, 12, 26 }, SWORD_SKILLS, DEFAULT_SPRITES, new int[] { 390, 390, 386, 390 }, 388); + } + + /** + * Gets the dagger. + * + * @return the dagger + */ + public static EquippedWeapon getDagger() { + return new EquippedWeapon(WeaponType.DAGGER, HitType.MELEE, 4, new int[] { 89, 12, 26 }, SWORD_SKILLS, DEFAULT_SPRITES, new int[] { 400, 402, 451, 400 }, 403); + } + + /** + * Gets the pick axe. + * + * @return the pick axe + */ + public static EquippedWeapon getPickAxe() { + return new EquippedWeapon(WeaponType.PICKAXE, HitType.MELEE, 4, new int[] { 89, 12, 26 }, SWORD_SKILLS, DEFAULT_SPRITES, new int[] { 401, 401, 400, 401 }, 397); + } + + /** + * Gets the axe. + * + * @return the axe + */ + public static EquippedWeapon getAxe() { + return new EquippedWeapon(WeaponType.AXE, HitType.MELEE, 6, new int[] { 75, 12, 26 }, new int[][] { { 0 }, { 1 }, { 2 }, { 2 } }, DEFAULT_SPRITES, new int[] { 395, 395, 401, 395 }, 397); + } + + /** + * Gets the warhammer. + * + * @return the warhammer + */ + public static EquippedWeapon getWarhammer() { + return new EquippedWeapon(WeaponType.WARHAMMER, HitType.MELEE, 6, new int[] { 76, -1, 26 }, SWORD_SKILLS, DEFAULT_SPRITES, new int[] { 401 }, 403); + } + + /** + * Gets the staff. + * + * @return the staff + */ + public static EquippedWeapon getStaff() { + return new EquippedWeapon(WeaponType.STAFF, HitType.MELEE, 5, new int[] { 90, -1, 9 }, new int[][] { { 0 }, { 2 }, { 1 } }, new int[] { 809, 1146, 1210 }, new int[] { 419 }, 420); + } + + /** + * Gets the godsword. + * + * @return the godsword + */ + public static EquippedWeapon getGodsword() { + return new EquippedWeapon(WeaponType.GODSWORD, HitType.MELEE, 6, new int[] { 81, 12, 26 }, SWORD_SKILLS, new int[] { 7047, 7046, 7039 }, new int[] { 7048, 7041, 7041, 7049 }, 7050); + } + + /** + * Gets the whip. + * + * @return the whip + */ + public static EquippedWeapon getWhip() { + return new EquippedWeapon(WeaponType.WHIP, HitType.MELEE, 4, new int[] { 93, 10, 24 }, new int[][] { { 0 }, { 0, 1, 2 }, { 1 } }, new int[] { 1832, 1660, 1661 }, new int[] { 1658 }, 1659); + } + + /** + * Gets the shortbow. + * + * @return the shortbow + */ + public static EquippedWeapon getShortbow() { + return new EquippedWeapon(WeaponType.SHORTBOW, HitType.RANGED, 4, new int[] { 77, 13, 27 }, RANGE_SKILLS, DEFAULT_SPRITES, new int[] { 426 }, 424); + } + + /** + * Gets the longbow. + * + * @return the longbow + */ + public static EquippedWeapon getLongbow() { + return new EquippedWeapon(WeaponType.LONGBOW, HitType.RANGED, 6, new int[] { 77, 13, 27 }, RANGE_SKILLS, DEFAULT_SPRITES, new int[] { 426 }, 424); + } + + /** + * Gets the throwing knife. + * + * @return the throwing knife + */ + public static EquippedWeapon getThrowingKnife() { + return new EquippedWeapon(WeaponType.THROWING_KNIFE, HitType.RANGED, 3, new int[] { 91, -1, 24 }, RANGE_SKILLS, DEFAULT_SPRITES, new int[] { 929 }, 424); + } + + /** The hit type. */ + private HitType hitType; + + /** The block animation. */ + private int blockAnimation, attackDelay; + + /** The tab info. */ + private int[] tabInfo, mobSprites, attackAnimations; + + /** The skills advanced. */ + private int[][] skillsAdvanced; + + /** The wep type. */ + private WeaponType wepType; + + /** + * The Enum WeaponType. + */ + enum WeaponType { + + FISTS, SWORD, LONGSWORD, TWO_HANDED_SWORD, GODSWORD, MACE, SCIMITAR, DAGGER, PICKAXE, AXE, WARHAMMER, STAFF, WHIP, SHORTBOW, LONGBOW, THROWING_KNIFE + } + + /** + * Instantiates a new equipped weapon. + * + * @param wepType + * the wep type + * @param hitType + * the hit type + * @param attackDelay + * the attack delay + * @param tabInfo + * the tab info + * @param skillsAdvanced + * the skills advanced + * @param mobSprites + * the mob sprites + * @param attackAnimations + * the attack animations + * @param blockAnimation + * the block animation + */ + public EquippedWeapon(WeaponType wepType, HitType hitType, int attackDelay, int[] tabInfo, int[][] skillsAdvanced, int[] mobSprites, int[] attackAnimations, int blockAnimation) { + this.wepType = wepType; + this.hitType = hitType; + this.attackDelay = attackDelay; + this.tabInfo = tabInfo; + this.mobSprites = mobSprites; + this.attackAnimations = attackAnimations; + this.blockAnimation = blockAnimation; + this.skillsAdvanced = skillsAdvanced; + } + + /** + * Gets the hit type. + * + * @return the hit type + */ + public HitType getHitType() { + return hitType; + } + + /** + * Gets the tab id. + * + * @return the tab id + */ + public int getTabId() { + return tabInfo[0]; + } + + /** + * Gets the retaliate id. + * + * @return the retaliate id + */ + public int getRetaliateId() { + return tabInfo[2]; + } + + /** + * Gets the spec button. + * + * @return the spec button + */ + public int getSpecButton() { + return tabInfo[1]; + } + + /** + * Gets the attack delay. + * + * @return the attack delay + */ + public int getAttackDelay() { + return attackDelay; + } + + /** + * Gets the wep type. + * + * @return the wep type + */ + public WeaponType getWepType() { + return wepType; + } + + /** + * Gets the block animation. + * + * @return the block animation + */ + public int getBlockAnimation() { + return blockAnimation; + } + + /** + * Gets the mob sprites. + * + * @return the mob sprites + */ + public int[] getMobSprites() { + return mobSprites; + } + + /** + * Gets the attack animations. + * + * @return the attack animations + */ + public int[] getAttackAnimations() { + return attackAnimations; + } + + /** + * Gets the skills. + * + * @return the skills + */ + public int[][] getSkills() { + return skillsAdvanced; + } + + /** + * Gets the type. + * + * @param weaponName + * the weapon name + * @return the type + */ + public static EquippedWeapon getType(String weaponName) { + if (weaponName == null) { + return getFists(); + } + weaponName = weaponName.toLowerCase(); + if (weaponName.contains("whip")) { + return getWhip(); + } else if (weaponName.endsWith("warhammer")) { + return getWarhammer(); + } else if (weaponName.endsWith("longsword") || weaponName.equals("excalibur") || weaponName.equals("brine sabre")) { + return getLongsword(); + } else if (weaponName.endsWith("battleaxe") || weaponName.endsWith(" axe")) { + return getAxe(); + } else if (weaponName.endsWith("godsword") || weaponName.endsWith("saradomin sword")) { + return getGodsword(); + } else if (weaponName.endsWith("scimitar")) { + return getScimitar(); + } else if (weaponName.contains("staff")) { + return getStaff(); + } else if (weaponName.endsWith("mace")) { + return getMace(); + } else if (weaponName.endsWith("pickaxe")) { + return getPickAxe(); + } else if (weaponName.endsWith("2h sword")) { + return get2hSword(); + } else if (weaponName.endsWith("shortbow")) { + return getShortbow(); + } else if (weaponName.endsWith("bow")) { + return getLongbow(); + } else if (weaponName.contains("dagger")) { + return getDagger(); + } else if (weaponName.contains("knife")) { + return getThrowingKnife(); + } else { + return getSword(); + } + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#clone() + */ + public EquippedWeapon clone() { + return new EquippedWeapon(wepType, hitType, attackDelay, tabInfo, skillsAdvanced, mobSprites, attackAnimations, blockAnimation); + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object object) { + if (object instanceof EquippedWeapon) { + EquippedWeapon other = (EquippedWeapon) object; + return (other.wepType == wepType && other.hitType == hitType && other.attackDelay == attackDelay && other.tabInfo == tabInfo && other.skillsAdvanced == skillsAdvanced && other.mobSprites == mobSprites && other.attackAnimations == attackAnimations && other.blockAnimation == blockAnimation); + } + return false; + } + + /** + * Array. + * + * @param src + * the src + * @return the int[] + */ + public static int[] array(int[] src) { + int[] copy = new int[src.length]; + System.arraycopy(src, 0, copy, 0, src.length); + return copy; + } + +} diff --git a/src/osiris/game/model/combat/Hit.java b/src/osiris/game/model/combat/Hit.java new file mode 100644 index 0000000..5b89c6b --- /dev/null +++ b/src/osiris/game/model/combat/Hit.java @@ -0,0 +1,400 @@ +package osiris.game.model.combat; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.util.ArrayList; + +import osiris.Main; +import osiris.ServerEngine; +import osiris.game.action.Action; +import osiris.game.action.impl.CombatAction; +import osiris.game.action.impl.TeleportAction; +import osiris.game.model.Character; +import osiris.game.model.Npc; +import osiris.game.model.Player; +import osiris.game.model.Skills; +import osiris.game.model.effect.ExpiringEffect; +import osiris.game.model.effect.PrayerEffect; +import osiris.game.model.ground.GroundItem; +import osiris.game.model.ground.GroundManager; +import osiris.game.model.item.Item; +import osiris.game.model.skills.Prayer; +import osiris.game.update.block.AnimationBlock; +import osiris.game.update.block.GraphicsBlock; +import osiris.util.StopWatch; + +// TODO: Auto-generated Javadoc +/** + * The Class Hit. + * + * @author Boomer + * + */ +public class Hit { + + /** + * The victim. + */ + private final Character attacker; + + /** The victim. */ + private Character victim; + + /** + * The tick delay. + */ + private final int delay; + + /** The graphic. */ + private int[] graphic; + + /** The hit. */ + private int hit; + + /** The max hit. */ + private final int maxHit; + + /** The override animation. */ + private int overrideAnimation; + + /** The leech. */ + private double leech; + + /** The drop arrow. */ + private Item dropArrow; + + /** The effects. */ + private ArrayList effects; + + /** + * The tick timer. + */ + private final StopWatch stopWatch; + + /** + * The attack type. + */ + private final HitType hitType; + + /** + * Instantiates a new hit. + * + * @param attacker + * the attacker + * @param victim + * the victim \ + * @param hitDamage + * the max damage + * @param delay + * the delay before the hit shows + * @param hitType + * the attack type + */ + public Hit(final Character attacker, final Character victim, int hitDamage, int delay, HitType hitType) { + this.victim = victim; + this.attacker = attacker; + this.hitType = hitType; + this.stopWatch = new StopWatch(); + this.hit = hitDamage; + this.maxHit = hit; + this.delay = delay; + this.leech = 0; + this.effects = new ArrayList(); + this.dropArrow = null; + this.overrideAnimation = -1; + this.graphic = new int[] { -1, 0 }; + if (attacker instanceof Player) { + if (victim != null && victim instanceof Player) { + PrayerEffect overHeadEffect = ((Player) victim).getPrayers().get(Prayer.Type.HEADICON); + if (overHeadEffect != null) { + Prayer overHead = overHeadEffect.getPrayer(); + if (overHead != null) { + switch (hitType) { + case MELEE: + if (overHead == Prayer.PROTECT_FROM_MELEE) + hit *= .4; + break; + case RANGED: + if (overHead == Prayer.PROTECT_FROM_MISSILES) + hit *= .4; + break; + case MAGIC: + if (overHead == Prayer.PROTECT_FROM_MAGIC) + hit *= .4; + break; + } + } + } + } + } + if (this.hit > 0) { + if (hitType == HitType.MELEE && victim != null) { + if (overrideAnimation != -1 && victim instanceof Player) + victim.addUpdateBlock(new AnimationBlock(victim, overrideAnimation, 0)); + else { + int block = victim.getBlockAnimation(); + if (block != -1) + victim.addUpdateBlock(new AnimationBlock(victim, block, 0)); + } + } + if (attacker instanceof Player && hitType != HitType.POISON && hitType != HitType.RECOIL && hitType != HitType.BURN) { + int[] skillsEarned; + if (hitType == HitType.MAGIC) + skillsEarned = new int[] { 6 }; + else + skillsEarned = ((Player) attacker).getEquippedAttack().getSkills()[((Player) attacker).getAttackStyle()]; + double experiencePerStat = 4.0 / skillsEarned.length; + for (int i = 0; i < skillsEarned.length; i++) + ((Player) attacker).getSkills().addExp(skillsEarned[i], hit * experiencePerStat); + ((Player) attacker).getSkills().addExp(Skills.SKILL_HITPOINTS, hit * 1.5); + } + } + } + + /** + * Sets the graphic. + * + * @param graphicId + * the graphic id + * @param height + * the height + */ + public void setGraphic(int graphicId, int height) { + this.graphic = new int[] { graphicId, height }; + } + + /** + * Sets the leech. + * + * @param leech + * the new leech + */ + public void setLeech(double leech) { + this.leech = leech; + } + + /** + * Sets the effects. + * + * @param effect + * the new effects + */ + public void setEffects(ExpiringEffect effect) { + this.effects.add(effect); + } + + /** + * Sets the drop arrow. + * + * @param item + * the new drop arrow + */ + public void setDropArrow(Item item) { + this.dropArrow = item; + } + + /** + * Execute. + * + * @return the int + */ + public boolean execute() { + if (victim == null) + return false; + Action currentAction = victim.getCurrentAction(); + if (currentAction instanceof TeleportAction) + return false; + if (victim.getCurrentAction() != null && !(victim.getCurrentAction() instanceof CombatAction)) + if (hitType != HitType.POISON) + victim.getCurrentAction().cancel(); + int currentHitpoints = victim.getCurrentHp(); + if (currentHitpoints == 0) + return false; + for (ExpiringEffect effect : effects) + if (victim.addEffect(effect)) + effect.execute(victim); + if (hit != -1) { + if (hit >= currentHitpoints) + hit = currentHitpoints; + if (attacker instanceof Npc) { + if (victim != null && victim instanceof Player) { + PrayerEffect overHeadEffect = ((Player) victim).getPrayers().get(Prayer.Type.HEADICON); + if (overHeadEffect != null) { + Prayer overHead = overHeadEffect.getPrayer(); + if (overHead != null) { + switch (hitType) { + case MELEE: + if (overHead == Prayer.PROTECT_FROM_MELEE) + hit = 0; + break; + case RANGED: + if (overHead == Prayer.PROTECT_FROM_MISSILES) + hit = 0; + break; + case MAGIC: + if (overHead == Prayer.PROTECT_FROM_MAGIC) + hit = 0; + break; + } + } + } + } + } + + if (leech != 0) { + int attackerHp = attacker.getCurrentHp(); + int adjustedHp = attackerHp + (int) Math.ceil(hit * leech); + int maxHp = attacker.getMaxHp(); + if (adjustedHp > attacker.getMaxHp()) + adjustedHp = maxHp; + attacker.setCurrentHp(adjustedHp); + } + + if (dropArrow != null) { + GroundItem item = new GroundItem(dropArrow, attacker, victim.getPosition()); + GroundManager.getManager().dropItem(item); + } + + // XXX: This way admins aren't killed by accident. This can be + // disabled by + // running the server in verbose mode. + boolean canDamage = true; + if (victim instanceof Player) { + if (((Player) victim).getPlayerStatus() == 2 && !Main.isLocal()) { + canDamage = false; + } + } + + if (canDamage) { + victim.setCurrentHp(currentHitpoints - hit); + } + if (victim instanceof Player) { + if (attacker instanceof Player) { + Player player = (Player) attacker; + Player vic = (Player) victim; + PrayerEffect revenge = player.getPrayers().get(Prayer.Type.HEADICON); + if (revenge != null && revenge.getPrayer() == Prayer.SMITE) { + double prayerSmited = hit / 4; + double currentPrayer = vic.getSkills().realCurrentLevel(Skills.SKILL_PRAYER); + double adjustedPrayer = currentPrayer -= prayerSmited; + if (adjustedPrayer < 0) + adjustedPrayer = 0; + vic.getSkills().setCurLevel(Skills.SKILL_PRAYER, adjustedPrayer); + } + + } + if (victim.getCurrentHp() > 0 && victim.getCurrentHp() < (victim.getMaxHp() * .1)) { + Player player = (Player) victim; + PrayerEffect revenge = player.getPrayers().get(Prayer.Type.HEADICON); + if (revenge != null && revenge.getPrayer() == Prayer.REDEMPTION) { + player.getSkills().setCurLevel(Skills.SKILL_PRAYER, 0); + player.addUpdateBlock(new GraphicsBlock(player, 436, 0)); + int currentHp = player.getCurrentHp(); + int prayerLevel = player.getSkills().maxLevel(Skills.SKILL_PRAYER); + int adjustedHp = currentHp += prayerLevel * .25; + int maxHp = player.getMaxHp(); + if (adjustedHp > maxHp) + adjustedHp = maxHp; + player.setCurrentHp(adjustedHp); + } + } + + } + + if (victim.getCurrentHp() != 0 && this.getAttackType() != HitType.MELEE && this.getAttackType() != HitType.POISON && this.getAttackType() != HitType.RECOIL) { + if (overrideAnimation != -1 && victim instanceof Player) + victim.addUpdateBlock(new AnimationBlock(victim, overrideAnimation, 0)); + else { + int block = victim.getBlockAnimation(); + if (block != -1) + victim.addUpdateBlock(new AnimationBlock(victim, block, 0)); + } + } + } + if (graphic[0] != -1) { + ServerEngine.staticGraphic(victim, graphic[0], graphic[1]); + } + if (attacker != null && hitType != HitType.POISON && hitType != HitType.BURN && hitType != HitType.RECOIL) { + if (victim instanceof Npc || (victim instanceof Player && ((Player) victim).getSettings().isAutoRetaliate())) { + if (!victim.getCombatManager().combatEnabled()) { + if (victim.getCurrentAction() == null) { + victim.getCombatManager().setCombatAction(new CombatAction(victim, attacker)); + victim.getMovementQueue().reset(); + } + } + } + } + return true; + } + + /** + * Gets the attacker. + * + * @return the attacker + */ + public Character getAttacker() { + return attacker; + } + + /** + * Gets the victim. + * + * @return the victim + */ + public Character getVictim() { + return victim; + } + + /** + * Gets the attack type. + * + * @return the attack type + */ + public HitType getAttackType() { + return hitType; + } + + /** + * Gets the timer. + * + * @return the timer + */ + public StopWatch getTimer() { + return stopWatch; + } + + /** + * Gets the delay. + * + * @return the delay + */ + public int getDelay() { + return delay; + } + + /** + * Gets the hit. + * + * @return the hit + */ + public int getHit() { + return hit; + } + +} diff --git a/src/osiris/game/model/combat/HitResult.java b/src/osiris/game/model/combat/HitResult.java new file mode 100644 index 0000000..85dd434 --- /dev/null +++ b/src/osiris/game/model/combat/HitResult.java @@ -0,0 +1,99 @@ +package osiris.game.model.combat; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.util.StopWatch; + +// TODO: Auto-generated Javadoc +/** + * The Class HitResult. + * + * @author Boomer + * + */ +public class HitResult { + + /** The attacker u id. */ + private final int damage, attackerUId; + + /** The attack type. */ + private final HitType hitType; + + /** The decay. */ + private StopWatch decay; + + /** + * Instantiates a new hit result. + * + * @param damage + * the damage + * @param hitType + * the attack type + * @param attackerUId + * the attacker u id + */ + public HitResult(int damage, HitType hitType, int attackerUId) { + this.damage = damage; + this.hitType = hitType; + this.attackerUId = attackerUId; + this.decay = new StopWatch(); + } + + /** + * Gets the damage. + * + * @return the damage + */ + public int getDamage() { + return damage; + } + + /** + * Gets the hit type. + * + * @return the hit type + */ + public int getHitType() { + if (hitType == HitType.POISON) + return 2; + else if (hitType == HitType.BURN) + return 3; + else + return getDamage() == 0 ? 0 : 1; + } + + /** + * Gets the attacker u id. + * + * @return the attacker u id + */ + public int getAttackerUId() { + return attackerUId; + } + + /** + * Gets the decay. + * + * @return the decay + */ + public int getDecay() { + return decay.elapsed(); + } + +} diff --git a/src/osiris/game/model/combat/HitType.java b/src/osiris/game/model/combat/HitType.java new file mode 100644 index 0000000..6452af6 --- /dev/null +++ b/src/osiris/game/model/combat/HitType.java @@ -0,0 +1,30 @@ +package osiris.game.model.combat; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +// TODO: Auto-generated Javadoc +/** + * The Enum HitType. + * + * @author Boomer + * + */ +public enum HitType { + MELEE, RANGED, MAGIC, POISON, BURN, RECOIL, OTHER +} diff --git a/src/osiris/game/model/combat/Projectile.java b/src/osiris/game/model/combat/Projectile.java new file mode 100644 index 0000000..ee46d47 --- /dev/null +++ b/src/osiris/game/model/combat/Projectile.java @@ -0,0 +1,118 @@ +package osiris.game.model.combat; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.model.Character; +import osiris.game.model.Player; +import osiris.game.model.Position; +import osiris.io.EventWriter; +import osiris.util.StopWatch; + +// TODO: Auto-generated Javadoc +/** + * The Class Projectile. + * + * @author Boomer + * + */ +public class Projectile { + + /** The to. */ + private Position from, to; + + /** The lock on. */ + private Character lockOn; + + /** The tick delay. */ + private int graphicId, angle, speed, tickDelay; + + /** The height. */ + private int[] height; + + /** The timer. */ + private StopWatch timer; + + /** + * Instantiates a new projectile. + * + * @param graphicId + * the graphic id + * @param from + * the from + * @param to + * the to + * @param angle + * the angle + * @param speed + * the speed + * @param height + * the height + * @param lockOn + * the lock on + * @param tickDelay + * the tick delay + */ + public Projectile(int graphicId, Position from, Position to, int angle, int speed, int[] height, Character lockOn, int tickDelay) { + this.graphicId = graphicId; + this.from = from; + this.to = to; + this.angle = angle; + this.speed = speed; + this.height = height; + this.lockOn = lockOn; + this.tickDelay = tickDelay; + this.timer = new StopWatch(); + } + + /** + * Should fire. + * + * @return true, if successful + */ + public boolean shouldFire() { + boolean should = timer.elapsed() >= tickDelay; + return should; + } + + /** + * Write projectile. + * + * @param writer + * the writer + */ + public void writeProjectile(EventWriter writer) { + writer.sendProjectile(from, to, graphicId, angle, height[0], height[1], speed, lockOn); + } + + /** + * Show. + * + * @param character + * the character + */ + public void show(Character character) { + if (character instanceof Player) { + writeProjectile(((Player) character).getEventWriter()); + } + for (Player player : character.getLocalPlayers()) + if (character.isInSight(player)) + writeProjectile(player.getEventWriter()); + } + +} diff --git a/src/osiris/game/model/combat/SpecialManager.java b/src/osiris/game/model/combat/SpecialManager.java new file mode 100644 index 0000000..3cbc98a --- /dev/null +++ b/src/osiris/game/model/combat/SpecialManager.java @@ -0,0 +1,99 @@ +package osiris.game.model.combat; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.model.Character; +import osiris.game.model.Player; +import osiris.game.model.combat.missile.RangedAttack; +import osiris.game.model.def.ItemDef; +import osiris.game.model.item.Item; +import osiris.util.Utilities; + +// TODO: Auto-generated Javadoc +/** + * The Class SpecialManager. + * + * @author Boomer + */ +public class SpecialManager { + + /** + * Gets the special. + * + * @param character + * the character + * @param victim + * the victim + * @param item + * the item + * @param damage + * the damage + * @param hitDelay + * the hit delay + * @param range + * the range + * @return the special + */ + public static Attack getSpecial(Character character, Character victim, Item item, int damage, int hitDelay, RangedAttack range) { + if (item == null) + return null; + int index = specWeaponIndex(item.getId()); + if (index == -1) + return null; + Attack attack = new Attack(); + if (character instanceof Player) { + boolean adjusted = ((Player) character).adjustSpecialEnergy(-(int) (1000 * ENERGY_REQUIRED[index])); + if (!adjusted) + return null; + } + switch (index) { + case 0: // dragon dagger + damage *= 1.1; + int firstHit = Utilities.random(damage); + int secondHit = Utilities.random(damage); + attack.setAnimation(new int[] { 0x426 }).setAttackGfx(new int[] { 252, 100 }).setHits(new int[][] { { firstHit, hitDelay }, { secondHit, hitDelay } }); + break; + } + + if (character instanceof Player) + ((Player) character).setSpecEnabled(false); + return attack; + } + + /** + * Spec weapon index. + * + * @param itemId + * the item id + * @return the int + */ + public static int specWeaponIndex(int itemId) { + String wepName = ItemDef.forId(itemId).getName(); + for (int i = 0; i < SPECIAL_WEAPONS.length; i++) + if (wepName.toLowerCase().startsWith(SPECIAL_WEAPONS[i])) + return i; + return -1; + } + + /** The Constant ENERGY_REQUIRED. */ + public static final double[] ENERGY_REQUIRED = { .25 }; + + /** The Constant SPECIAL_WEAPONS. */ + public static final String[] SPECIAL_WEAPONS = { "dragon dagger" }; +} diff --git a/src/osiris/game/model/combat/impl/MagicAttack.java b/src/osiris/game/model/combat/impl/MagicAttack.java new file mode 100644 index 0000000..5aea7e4 --- /dev/null +++ b/src/osiris/game/model/combat/impl/MagicAttack.java @@ -0,0 +1,93 @@ +package osiris.game.model.combat.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.model.Character; +import osiris.game.model.combat.Attack; +import osiris.game.model.combat.HitType; +import osiris.game.model.magic.MagicManager; +import osiris.game.model.magic.Spell; +import osiris.game.model.magic.SpellBook; + +// TODO: Auto-generated Javadoc +/** + * The Class MagicAttack. + * + * @author Boomer + */ +public class MagicAttack extends Attack { + + /** The spell. */ + private Spell spell; + + /** The defence. */ + private boolean defence; + + /** + * Instantiates a new magic attack. + * + * @param spell + * the spell + */ + public MagicAttack(Spell spell) { + this(spell, false); + } + + /** + * Instantiates a new magic attack. + * + * @param spell + * the spell + * @param book + * the book + */ + public MagicAttack(int spell, SpellBook book) { + this(MagicManager.getSpell(spell, book)); + } + + /** + * Instantiates a new magic attack. + * + * @param spell + * the spell + * @param defence + * the defence + */ + public MagicAttack(Spell spell, boolean defence) { + this.spell = spell; + this.defence = defence; + setHitType(HitType.MAGIC); + } + + /* + * (non-Javadoc) + * + * @see osiris.game.model.combat.Attack#execute(osiris.game.model.Character, + * osiris.game.model.Character, boolean) + */ + @Override + public int execute(Character character, Character victim, boolean random) { + if (spell == null) + return -1; + boolean executed = spell.execute(character, defence, victim); + if (!executed) + return -1; + return spell.getDelay(); + } +} diff --git a/src/osiris/game/model/combat/impl/PlayerAttack.java b/src/osiris/game/model/combat/impl/PlayerAttack.java new file mode 100644 index 0000000..7a40029 --- /dev/null +++ b/src/osiris/game/model/combat/impl/PlayerAttack.java @@ -0,0 +1,114 @@ +package osiris.game.model.combat.impl; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.util.Random; + +import osiris.game.model.Character; +import osiris.game.model.Player; +import osiris.game.model.combat.Attack; +import osiris.game.model.combat.CombatUtilities; +import osiris.game.model.combat.EquippedWeapon; +import osiris.game.model.combat.HitType; +import osiris.game.model.combat.SpecialManager; +import osiris.game.model.combat.missile.RangedAttack; +import osiris.game.model.item.Item; +import osiris.util.Settings; +import osiris.util.Utilities; + +// TODO: Auto-generated Javadoc +/** + * The Class PlayerAttack. + * + * @author Boomer + */ +public class PlayerAttack extends Attack { + + /** The weapon. */ + private EquippedWeapon weapon; + + /** + * Instantiates a new player attack. + * + * @param weapon + * the weapon + */ + public PlayerAttack(EquippedWeapon weapon) { + setAnimation(weapon.getAttackAnimations()); + setHitType(weapon.getHitType()); + setDelay(weapon.getAttackDelay()); + this.weapon = weapon; + } + + /* + * (non-Javadoc) + * + * @see osiris.game.model.combat.Attack#execute(osiris.game.model.Character, + * osiris.game.model.Character, boolean) + */ + @Override + public int execute(Character character, Character other, boolean random) { + RangedAttack ranged = null; + if (weapon.getHitType() == HitType.RANGED) { + ranged = character.getCombatManager().getRangedAttack(); + if (ranged == null) + return -1; + } + int maxHit = CombatUtilities.calculateMaxDamage(character, weapon, ranged); + int hitDelay = 0; + int amountOfHits = 1; + int hits[][] = null; + if (ranged != null) { + if (ranged.getArrow() == null) + amountOfHits = 1; + else + amountOfHits = ranged.getArrow().getAmount(); + int[][] projectiles = new int[amountOfHits][4]; + setAttackGfx(new int[] { ranged.getPullback(), 100 }); + for (int i = 0; i < amountOfHits; i++) { + projectiles[i][0] = ranged.getProjectileId(); + projectiles[i][1] = 90 + (i * 20); + projectiles[i][2] = 46 + (i * 20); + projectiles[i][3] = 0; + } + setProjectiles(projectiles); + hitDelay = 3; + hits = new int[amountOfHits][3]; + } else + hits = new int[amountOfHits][2]; + if ((character instanceof Player && ((Player) character).isSpecEnabled())) { + Player player = (Player) character; + Item item = player.getEquipment().getItem(Settings.SLOT_WEAPON); + Attack special = SpecialManager.getSpecial(player, other, item, maxHit, hitDelay, ranged); + if (special != null) { + return special.setDelay(getAttackDelay()).execute(player, other, false); + } + } + for (int i = 0; i < amountOfHits; i++) { + int randomDamage = Utilities.random(maxHit); + hits[i][0] = randomDamage; + hits[i][1] = hitDelay + i; + if (hits[i].length > 2 && ranged != null && ranged.getArrow() != null) + hits[i][2] = new Random().nextDouble() <= .85 ? ranged.getArrow().getId() : -1; + } + setHits(hits); + return super.execute(character, other, false); + } + +} diff --git a/src/osiris/game/model/combat/missile/Arrow.java b/src/osiris/game/model/combat/missile/Arrow.java new file mode 100644 index 0000000..3f42c0d --- /dev/null +++ b/src/osiris/game/model/combat/missile/Arrow.java @@ -0,0 +1,124 @@ +package osiris.game.model.combat.missile; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.model.Character; +import osiris.game.model.Npc; +import osiris.game.model.Player; +import osiris.game.model.def.ItemDef; +import osiris.game.model.item.Item; + +// TODO: Auto-generated Javadoc +/** + * The Enum Arrow. + * + * @author Boomer + */ +public enum Arrow { + + DRAGON(1120, 1116, 1111, 60), RUNE(15, 24, 1109, 49), ADAMANT(13, 22, 1108, 31), MITHRIL(12, 21, 1107, 22), BLACK(14, 23, -1, 18), STEEL(11, 20, 1106, 16), IRON(9, 18, 1105, 10), BRONZE(10, 19, 1104, 7), ICE(16, 25, 1110, 16), FIRE(17, 26, 1113, 10); + + /** The Constant ARROWS. */ + public static final Arrow[] ARROWS = { DRAGON, RUNE, ADAMANT, MITHRIL, BLACK, STEEL, IRON, BRONZE, ICE, FIRE }; + + /** The double pull back. */ + private int projectileId, pullback, rangeBonus, doublePullBack; + + /** + * Instantiates a new arrow. + * + * @param projectileId + * the projectile id + * @param pullback + * the pullback + * @param doublePullBack + * the double pull back + * @param rangeBonus + * the range bonus + */ + Arrow(int projectileId, int pullback, int doublePullBack, int rangeBonus) { + this.projectileId = projectileId; + this.pullback = pullback; + this.rangeBonus = rangeBonus; + this.doublePullBack = doublePullBack; + } + + /** + * Gets the ammo. + * + * @param character + * the character + * @param slot + * the slot + * @return the ammo + */ + public static RangedAttack getAmmo(Character character, int slot) { + return getAmmo(character, slot, 1, true); + } + + /** + * Gets the ammo. + * + * @param character + * the character + * @param slot + * the slot + * @param arrows + * the arrows + * @param requiresAll + * the requires all + * @return the ammo + */ + public static RangedAttack getAmmo(Character character, int slot, int arrows, boolean requiresAll) { + try { + Arrow found = null; + String itemKey = "arrows"; + if (character instanceof Npc) { + found = BRONZE; + return new RangedAttack(found.projectileId, found.pullback, 0, null); + } else if (character instanceof Player) { + Item item = ((Player) character).getEquipment().getItem(slot); + if (requiresAll) { + if (item == null || !((Player) character).getEquipment().removeBySlot(slot, arrows)) { + ((Player) character).getEventWriter().sendMessage("You do not have enough ammo!"); + arrows = 0; + } + } else + for (int i = 0; i < arrows; i++) { + if (item == null || !((Player) character).getEquipment().removeBySlot(slot, 1)) { + ((Player) character).getEventWriter().sendMessage("You do not have enough ammo!"); + arrows = i; + break; + } + } + if (arrows == 0) + return null; + String name = ItemDef.forId(item.getId()).getName().replaceAll(" " + itemKey, "").replaceAll(" arrow", "").replaceAll("\\(e\\)", "").replaceAll("\\+", "").replaceAll("\\(p", "").replaceAll("\\)", ""); + for (Arrow arrow : ARROWS) { + if (arrow.toString().equalsIgnoreCase(name)) { + return new RangedAttack(arrow.projectileId, ((arrows == 2 && arrow.doublePullBack != -1) ? arrow.doublePullBack : arrow.pullback), arrow.rangeBonus, Item.create(item.getId(), arrows)); + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } +} diff --git a/src/osiris/game/model/combat/missile/Knife.java b/src/osiris/game/model/combat/missile/Knife.java new file mode 100644 index 0000000..ef6b0f5 --- /dev/null +++ b/src/osiris/game/model/combat/missile/Knife.java @@ -0,0 +1,103 @@ +package osiris.game.model.combat.missile; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.model.Character; +import osiris.game.model.Npc; +import osiris.game.model.Player; +import osiris.game.model.def.ItemDef; +import osiris.game.model.item.Item; + +// TODO: Auto-generated Javadoc +/** + * The Enum Knife. + * + * @author Boomer + */ +public enum Knife { + + // DRAGON(), + /** The RUNE. */ + RUNE(218, 225, 24), + /** The ADAMANT. */ + ADAMANT(217, 224, 14), + /** The MITHRIL. */ + MITHRIL(216, 223, 10), + /** The BLACK. */ + BLACK(215, 222, 8), + /** The STEEL. */ + STEEL(214, 221, 7), + /** The IRON. */ + IRON(213, 220, 4), + /** The BRONZE. */ + BRONZE(212, 219, 3); + + /** The Constant KNIVES. */ + public static final Knife[] KNIVES = { RUNE, ADAMANT, MITHRIL, BLACK, STEEL, IRON, BRONZE }; + + /** The range bonus. */ + private int projectileId, pullback, rangeBonus; + + /** + * Instantiates a new knife. + * + * @param projectileId + * the projectile id + * @param pullback + * the pullback + * @param rangeBonus + * the range bonus + */ + Knife(int projectileId, int pullback, int rangeBonus) { + this.projectileId = projectileId; + this.pullback = pullback; + this.rangeBonus = rangeBonus; + } + + /** + * Gets the ammo. + * + * @param character + * the character + * @param slot + * the slot + * @return the ammo + */ + public static RangedAttack getAmmo(Character character, int slot) { + Knife found = null; + String itemKey = "knife"; + if (character instanceof Npc) { + found = BRONZE; + return new RangedAttack(found.projectileId, found.pullback, 0, null); + } else if (character instanceof Player) { + Item item = ((Player) character).getEquipment().getItem(slot); + if (item == null || !((Player) character).getEquipment().removeBySlot(slot, 1)) { + ((Player) character).getEventWriter().sendMessage("You do not have enough ammo!"); + return null; + } + String name = ItemDef.forId(item.getId()).getName().replaceAll(" " + itemKey, "").replaceAll("\\(e\\)", "").replaceAll("\\+", "").replaceAll("\\(p", "").replaceAll("\\)", ""); + for (Knife knife : KNIVES) { + if (knife.toString().equalsIgnoreCase(name)) { + return new RangedAttack(knife.projectileId, knife.pullback, knife.rangeBonus, Item.create(item.getId(), 1)); + } + } + } + return null; + } +} \ No newline at end of file diff --git a/src/osiris/game/model/combat/missile/RangedAttack.java b/src/osiris/game/model/combat/missile/RangedAttack.java new file mode 100644 index 0000000..1663a46 --- /dev/null +++ b/src/osiris/game/model/combat/missile/RangedAttack.java @@ -0,0 +1,96 @@ +package osiris.game.model.combat.missile; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.model.item.Item; + +// TODO: Auto-generated Javadoc +/** + * The Class RangedAttack. + * + * @author Boomer + */ +@SuppressWarnings("unused") +public class RangedAttack { + + /** The range bonus. */ + private int projectileId, pullback, rangeBonus; + + /** The arrow. */ + private Item arrow; + + /** The chance spec. */ + private double chanceSpec; + + /** + * Instantiates a new ranged attack. + * + * @param projectileId + * the projectile id + * @param pullback + * the pullback + * @param rangeBonus + * the range bonus + * @param arrow + * the arrow + */ + public RangedAttack(int projectileId, int pullback, int rangeBonus, Item arrow) { + this.rangeBonus = rangeBonus; + this.projectileId = projectileId; + this.pullback = pullback; + this.arrow = arrow; + } + + /** + * Gets the projectile id. + * + * @return the projectile id + */ + public int getProjectileId() { + return projectileId; + } + + /** + * Gets the pullback. + * + * @return the pullback + */ + public int getPullback() { + return pullback; + } + + /** + * Gets the arrow. + * + * @return the arrow + */ + public Item getArrow() { + return arrow; + } + + /** + * Gets the range bonus. + * + * @return the range bonus + */ + public int getRangeBonus() { + return rangeBonus; + } + +} diff --git a/src/osiris/game/model/def/GameDef.java b/src/osiris/game/model/def/GameDef.java new file mode 100644 index 0000000..095c65f --- /dev/null +++ b/src/osiris/game/model/def/GameDef.java @@ -0,0 +1,103 @@ +package osiris.game.model.def; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.awt.Dimension; + +// TODO: Auto-generated Javadoc + +/** + * The Class GameDef. + * + * @author Boomer + * + */ +public class GameDef { + + /** + * The id. + */ + private final int id; + + /** + * The examine. + */ + private final String name, examine; + + /** + * The dim. + */ + private final Dimension dim; + + /** + * Instantiates a new game def. + * + * @param id + * the id + * @param name + * the name + * @param examine + * the examine + * @param dim + * the dim + */ + public GameDef(final int id, String name, String examine, final Dimension dim) { + this.id = id; + this.name = name; + this.examine = examine; + this.dim = dim; + } + + /** + * Gets the id. + * + * @return the id + */ + public final int getId() { + return id; + } + + /** + * Gets the dimension. + * + * @return the dimension + */ + public final Dimension getDimension() { + return dim; + } + + /** + * Gets the name. + * + * @return the name + */ + public String getName() { + return name; + } + + /** + * Gets the examine. + * + * @return the examine + */ + public String getExamine() { + return examine; + } + +} diff --git a/src/osiris/game/model/def/ItemDef.java b/src/osiris/game/model/def/ItemDef.java new file mode 100644 index 0000000..ceb7c2e --- /dev/null +++ b/src/osiris/game/model/def/ItemDef.java @@ -0,0 +1,488 @@ +package osiris.game.model.def; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.io.BufferedReader; +import java.io.FileInputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.ObjectInputStream; + +import osiris.util.Settings; + +// TODO: Auto-generated Javadoc +/** + * The Class ItemDef. + * + * @author Boomer + */ +@SuppressWarnings("unused") +public class ItemDef extends GameDef { + + /** The definitions. */ + public static GameDef[] definitions; + + /** The female id. */ + private int equipId, specialtyStorePrice, price, femaleId; + + /** The weight. */ + private double weight; + + /** The bonuses. */ + private int[] requirements, bonuses; + + /** The tradeable. */ + private boolean noted, stackable, members, tradeable; + + /** The Constant ITEM_DEFINITIONS. */ + public static final int ITEM_DEFINITIONS = 13559; + + /** + * Instantiates a new game def. + * + * @param id + * the id + * @param equipId + * the equip id + * @param bonuses + * the bonuses + * @param noted + * the noted + * @param stackable + * the stackable + * @param members + * the members + * @param weight + * the weight + * @param price + * the price + * @param name + * the name + * @param examine + * the examine + * @param levelRequirements + * the level requirements + */ + + public ItemDef(final int id, final int equipId, int[] bonuses, boolean noted, boolean stackable, boolean members, double weight, int price, String name, String examine, int[] levelRequirements) { + super(id, name, examine, Settings.SINGULAR_DIMENSION); + this.equipId = equipId; + this.bonuses = bonuses; + this.noted = noted; + this.stackable = stackable; + this.members = members; + this.femaleId = -1; + this.tradeable = true; + this.weight = weight; + this.price = price; + this.requirements = levelRequirements; + } + + /** + * Sets the stackable. + * + * @param stackable + * the new stackable + */ + public void setStackable(boolean stackable) { + this.stackable = stackable; + } + + /** + * Sets the tradeable. + * + * @param tradeable + * the new tradeable + */ + public void setTradeable(boolean tradeable) { + this.tradeable = tradeable; + } + + /** + * Sets the female id. + * + * @param femaleId + * the new female id + */ + public void setFemaleId(int femaleId) { + this.femaleId = femaleId; + } + + /** + * Sets the bonuses. + * + * @param bonuses + * the new bonuses + */ + public void setBonuses(int[] bonuses) { + this.bonuses = bonuses; + } + + /** + * Sets the price. + * + * @param price + * the new price + */ + public void setPrice(int price) { + this.price = price; + } + + /** + * Gets the female id. + * + * @return the female id + */ + public int getFemaleId() { + return femaleId; + } + + /** + * Gets the requirements. + * + * @return the requirements + */ + public int[] getRequirements() { + return requirements; + } + + /** + * Checks if is tradeable. + * + * @return true, if is tradeable + */ + public boolean isTradeable() { + if (noted) + return ItemDef.forId(getId() - 1).isTradeable(); + else + return tradeable; + } + + /** + * Checks if is members. + * + * @return true, if is members + */ + public boolean isMembers() { + return members; + } + + /** + * Gets the weight. + * + * @return the weight + */ + public double getWeight() { + return weight; + } + + /** + * Gets the price. + * + * @return the price + */ + public int getPrice() { + return price; + } + + /** + * Gets the equip id. + * + * @return the equip id + */ + public int getEquipId() { + return equipId; + } + + /** + * Gets the bonuses. + * + * @return the bonuses + */ + public int[] getBonuses() { + return bonuses; + } + + /** + * Gets the bonus. + * + * @param id + * the id + * @return the bonus + */ + public int getBonus(int id) { + return bonuses[id]; + } + + /** + * Checks if is noted. + * + * @return true, if is noted + */ + public boolean isNoted() { + return noted; + } + + /** + * Checks if is stackable. + * + * @return true, if is stackable + */ + public boolean isStackable() { + return stackable || noted; + } + + /** + * Checks if is stackable. + * + * @return true, if is stackable + */ + public boolean isRawStackable() { + return stackable; + } + + /** + * Checks if is full body. + * + * @return true, if is full body + */ + public boolean isFullBody() { + String weapon = getName(); + for (int i = 0; i < Settings.FULL_BODY.length; i++) { + if (weapon.contains(Settings.FULL_BODY[i])) { + return true; + } + } + return false; + } + + /** + * Checks if is full helm. + * + * @return true, if is full helm + */ + public boolean isFullHelm() { + String weapon = getName(); + for (int i = 0; i < Settings.FULL_HAT.length; i++) { + if (weapon.endsWith(Settings.FULL_HAT[i])) { + return true; + } + } + return false; + } + + /** + * Checks if is full mask. + * + * @return true, if is full mask + */ + public boolean isFullMask() { + String weapon = getName(); + for (int i = 0; i < Settings.FULL_MASK.length; i++) { + if (weapon.endsWith(Settings.FULL_MASK[i])) { + return true; + } + } + return false; + } + + /** + * Checks if is two handed. + * + * @return true, if is two handed + */ + public boolean isTwoHanded() { + String wepEquiped = getName(); + int itemId = getId(); + if (itemId == 4212) + return true; + else if (itemId == 4214) + return true; + else if (wepEquiped.endsWith("2h sword")) + return true; + else if (wepEquiped.endsWith("longbow")) + return true; + else if (wepEquiped.equals("Seercull")) + return true; + else if (wepEquiped.endsWith("shortbow")) + return true; + else if (wepEquiped.endsWith("Longbow")) + return true; + else if (wepEquiped.endsWith("Shortbow")) + return true; + else if (wepEquiped.endsWith("bow full")) + return true; + else if (wepEquiped.endsWith("halberd")) + return true; + else if (wepEquiped.equals("Granite maul")) + return true; + else if (wepEquiped.equals("Karils crossbow")) + return true; + else if (wepEquiped.equals("Torags hammers")) + return true; + else if (wepEquiped.equals("Veracs flail")) + return true; + else if (wepEquiped.equals("Dharoks greataxe")) + return true; + else if (wepEquiped.equals("Guthans warspear")) + return true; + else if (wepEquiped.equals("Tzhaar-ket-om")) + return true; + else if (wepEquiped.endsWith("godsword")) + return true; + else if (wepEquiped.equals("Saradomin sword")) + return true; + else if (wepEquiped.equalsIgnoreCase("dark bow")) + return true; + else if (wepEquiped.startsWith("Anger")) + return true; + else + return false; + } + + /** + * Checks if is shield. + * + * @return true, if is shield + */ + public boolean isShield() { + String name = getName(); + for (int i = 0; i < Settings.SHIELDS.length; i++) { + if (name.contains(Settings.SHIELDS[i])) { + return true; + } + } + return false; + } + + /** + * Gets the item type. + * + * @return the item type + */ + public int getItemType() { + String name = getName().toLowerCase(); + for (int i = 0; i < Settings.CAPES.length; i++) { + if (name.contains(Settings.CAPES[i].toLowerCase())) + return 1; + } + for (int i = 0; i < Settings.HATS.length; i++) { + if (name.contains(Settings.HATS[i].toLowerCase())) + return 0; + } + for (int i = 0; i < Settings.BOOTS.length; i++) { + if (name.endsWith(Settings.BOOTS[i]) || name.startsWith(Settings.BOOTS[i])) + return 10; + } + for (int i = 0; i < Settings.GLOVES.length; i++) { + if (name.endsWith(Settings.GLOVES[i]) || name.startsWith(Settings.GLOVES[i])) + return 9; + } + for (int i = 0; i < Settings.SHIELDS.length; i++) { + if (name.contains(Settings.SHIELDS[i].toLowerCase())) + return 5; + } + for (int i = 0; i < Settings.AMULETS.length; i++) { + if (name.endsWith(Settings.AMULETS[i]) || name.startsWith(Settings.AMULETS[i]) || name.contains(Settings.AMULETS[i])) + return 2; + } + for (int i = 0; i < Settings.ARROWS.length; i++) { + if (name.toLowerCase().contains(Settings.ARROWS[i].toLowerCase()) || name.startsWith(Settings.ARROWS[i])) + return 13; + } + for (int i = 0; i < Settings.RINGS.length; i++) { + if (name.endsWith(Settings.RINGS[i]) || name.startsWith(Settings.RINGS[i])) + return 12; + } + for (int i = 0; i < Settings.BODY.length; i++) { + if (name.contains(Settings.BODY[i].toLowerCase())) + return 4; + } + for (int i = 0; i < Settings.LEGS.length; i++) { + if (name.contains(Settings.LEGS[i].toLowerCase())) + return 7; + } + return 3; + } + + /** + * Load. + */ + public static void load() { + try { + definitions = new GameDef[ITEM_DEFINITIONS]; + FileInputStream input = new FileInputStream("./data/itemdefs/itemdefinitions.bin"); + ObjectInputStream in = new ObjectInputStream(input); + for (int i = 0; i < ITEM_DEFINITIONS; i++) { + int id = in.readInt(); + int equipId = in.readInt(); + int[] bonus = (int[]) in.readObject(); + boolean noted = in.readBoolean(); + boolean stackable = in.readBoolean(); + boolean members = in.readBoolean(); + double weight = in.readDouble(); + int price = in.readInt(); + String name = (String) in.readObject(); + String examine = (String) in.readObject(); + int[] requirements = (int[]) in.readObject(); + ItemDef def = new ItemDef(id, equipId, bonus, noted, stackable, members, weight, price, name, examine, requirements); + definitions[def.getId()] = def; + } + BufferedReader reader = new BufferedReader(new FileReader("./data/itemdefs/untradeable.txt")); + String line; + while ((line = reader.readLine()) != null) + if (!line.startsWith("//")) + ItemDef.forId(Integer.parseInt(line)).setTradeable(false); + ItemDef.forId(313).setStackable(true); + } catch (IOException ex) { + ex.printStackTrace(); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + } + + /** + * For id. + * + * @param id + * the id + * @return the item def + */ + public static ItemDef forId(int id) { + if (id < 0 || id > definitions.length) + return produceDefinition(id); + ItemDef def = (ItemDef) definitions[id]; + if (def == null) + return produceDefinition(id); + else + return def; + } + + /** + * Produce definition. + * + * @param itemId + * the item id + * @return the item def + */ + public static ItemDef produceDefinition(int itemId) { + return new ItemDef(itemId, -1, null, false, false, true, 0, 0, "Null", "this item has no definition", null); + } + +} diff --git a/src/osiris/game/model/def/NpcCombatDef.java b/src/osiris/game/model/def/NpcCombatDef.java new file mode 100644 index 0000000..9f1b9c2 --- /dev/null +++ b/src/osiris/game/model/def/NpcCombatDef.java @@ -0,0 +1,346 @@ +package osiris.game.model.def; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Random; +import java.util.StringTokenizer; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import osiris.game.model.combat.Attack; +import osiris.game.model.combat.HitType; +import osiris.game.model.combat.impl.MagicAttack; +import osiris.game.model.effect.CombatEffect; +import osiris.game.model.magic.SpellBook; + +// TODO: Auto-generated Javadoc +/** + * The Class NpcCombatDef. + * + * @author Boomer + */ +public class NpcCombatDef { + + /** The combat defs. */ + private static HashMap combatDefs; + + /** The death animation. */ + private int maxHealth = 0, blockAnimation = 424, deathAnimation = 7197; + + /** The attacks. */ + private Attack[] attacks = { new Attack() }; + + /** + * Instantiates a new npc combat def. + */ + private NpcCombatDef() { + } + + /** + * Instantiates a new npc combat def. + * + * @param maxHealth + * the max health + * @param attacks + * the attacks + * @param blockAnimation + * the block animation + * @param deathAnimation + * the death animation + */ + public NpcCombatDef(int maxHealth, Attack[] attacks, int blockAnimation, int deathAnimation) { + this.maxHealth = maxHealth; + this.attacks = attacks; + this.blockAnimation = blockAnimation; + this.deathAnimation = deathAnimation; + } + + /** + * For id. + * + * @param id + * the id + * @return the npc combat def + */ + public static NpcCombatDef forId(int id) { + NpcCombatDef existing = combatDefs.get(id); + if (existing == null) + return new NpcCombatDef(); + else + return existing.clone(); + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#clone() + */ + protected NpcCombatDef clone() { + return new NpcCombatDef(maxHealth, attacks, blockAnimation, deathAnimation); + } + + /** + * Gets the attacks. + * + * @return the attacks + */ + public Attack[] getAttacks() { + return attacks; + } + + /** + * Gets the attack. + * + * @return the attack + */ + public Attack getAttack() { + return attacks[new Random().nextInt(attacks.length)]; + } + + /** + * Gets the max hp. + * + * @return the max hp + */ + public int getMaxHp() { + return maxHealth; + } + + /** + * Gets the block animation. + * + * @return the block animation + */ + public int getBlockAnimation() { + return blockAnimation; + } + + /** + * Gets the death animation. + * + * @return the death animation + */ + public int getDeathAnimation() { + return deathAnimation; + } + + /** + * Load. + */ + public static void load() { + HashMap list = new HashMap(); + try { + DocumentBuilderFactory f = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = f.newDocumentBuilder(); + Document doc = builder.parse(new File("./data/config/npc-combat.xml")); + doc.getDocumentElement().normalize(); + + NodeList defList = (NodeList) doc.getElementsByTagName("definition"); + if (defList == null) + return; + for (int i = 0; i < defList.getLength(); i++) { + Element combatDef = (Element) defList.item(i); + // Stuff to set + List npcIds = new ArrayList(); + List attacks = new ArrayList(); + int health = 0, blockAnim = 424, deathAnim = 7197; + + Element npcIdsElement = (Element) combatDef.getElementsByTagName("ids").item(0); + if (npcIdsElement != null) { + String value = npcIdsElement.getFirstChild().getNodeValue(); + StringTokenizer st = new StringTokenizer(value, ","); + while (st.hasMoreTokens()) { + Integer id = Integer.parseInt(st.nextToken()); + npcIds.add(id); + } + } + NodeList itemsList = (NodeList) combatDef.getElementsByTagName("info"); + for (int n = 0; n < itemsList.getLength(); n++) { + Element itemElement = (Element) itemsList.item(n); + health = Integer.parseInt(itemElement.getAttribute("health")); + blockAnim = Integer.parseInt(itemElement.getAttribute("block")); + deathAnim = Integer.parseInt(itemElement.getAttribute("death")); + } + + NodeList magicList = (NodeList) combatDef.getElementsByTagName("spell"); + if (magicList != null) { + for (int m = 0; m < magicList.getLength(); m++) { + Element mageElement = (Element) magicList.item(m); + int spell = Integer.parseInt(mageElement.getAttribute("id")); + SpellBook spellBook = SpellBook.valueOf(mageElement.getAttribute("book").toUpperCase()); + attacks.add(new MagicAttack(spell, spellBook)); + } + } + NodeList customList = (NodeList) combatDef.getElementsByTagName("custom"); + if (customList != null) { + for (int c = 0; c < customList.getLength(); c++) { + Element customElement = (Element) customList.item(c); + String className = ""; + int chance = 100; + for (int a = 0; a < customElement.getAttributes().getLength(); a++) { + Node node = customElement.getAttributes().item(a); + String attribute = node.getNodeName(); + if (attribute.equalsIgnoreCase("class")) { + className = node.getNodeValue(); + } else if (attribute.equalsIgnoreCase("chance")) { + chance = Integer.parseInt(node.getNodeValue()); + } + } + Attack attack = Attack.create(className); + attack.setChance(chance); + attacks.add(attack); + } + } + NodeList attacksList = (NodeList) combatDef.getElementsByTagName("attack"); + if (attacksList != null) { + for (int att = 0; att < attacksList.getLength(); att++) { + Attack attack = new Attack(); + Element attackElement = (Element) attacksList.item(att); + for (int a = 0; a < attackElement.getAttributes().getLength(); a++) { + Node node = attackElement.getAttributes().item(a); + String attribute = node.getNodeName(); + if (attribute.equalsIgnoreCase("type")) { + attack.setHitType(HitType.valueOf(node.getNodeValue().toUpperCase())); + } else if (attribute.equalsIgnoreCase("delay")) { + attack.setDelay(Integer.parseInt(node.getNodeValue())); + } else if (attribute.equalsIgnoreCase("chance")) { + attack.setChance(Integer.parseInt(node.getNodeValue())); + } else if (attribute.equalsIgnoreCase("animation")) { + int animations[]; + StringTokenizer st = new StringTokenizer(node.getNodeValue(), ","); + animations = new int[st.countTokens()]; + int index = 0; + while (st.hasMoreTokens()) { + Integer id = Integer.parseInt(st.nextToken()); + animations[index] = id; + index++; + } + attack.setAnimation(animations); + } else if (attribute.equalsIgnoreCase("projectile")) { + ArrayList projectiles = new ArrayList(); + StringTokenizer st = new StringTokenizer(node.getNodeValue(), ","); + Integer[] projectile = new Integer[4]; + int index = 0; + if (st.countTokens() == 1) { + projectile[0] = Integer.parseInt(st.nextToken()); + projectile[1] = 90; + projectile[2] = 46; + projectile[3] = 0; + } else + while (st.hasMoreTokens()) { + Integer id = Integer.parseInt(st.nextToken()); + projectile[index] = id; + index++; + } + projectiles.add(projectile); + int[][] projectilez = new int[projectiles.size()][]; + for (int p = 0; p < projectiles.size(); p++) { + projectilez[p] = new int[projectiles.get(p).length]; + for (int p2 = 0; p2 < projectiles.get(p).length; p2++) + projectilez[p][p2] = projectiles.get(p)[p2]; + } + attack.setProjectiles(projectilez); + } else if (attribute.equalsIgnoreCase("effect")) { + StringTokenizer allEffects = new StringTokenizer(node.getNodeValue(), ";"); + while (allEffects.hasMoreTokens()) { + StringTokenizer aEffect = new StringTokenizer(allEffects.nextToken(), ","); + Object[] effect = new Object[aEffect.countTokens()]; + int index = 0; + while (aEffect.hasMoreTokens()) { + effect[index] = aEffect.nextToken(); + index++; + } + int effectIndex = Integer.parseInt((String) effect[0]); + int effectChance = Integer.parseInt((String) effect[1]); + Class theClass = (Class) Class.forName("osiris.game.model.effect." + effect[2]); + int cooldown = Integer.parseInt((String) effect[3]); + int amount = effect.length - 4; + Object[] params = new Object[amount]; + System.arraycopy(effect, 4, params, 0, amount); + attack.addEffect(effectIndex, effectChance, theClass, cooldown, params); + } + + } else if (attribute.equalsIgnoreCase("attackgfx")) { + int[] attackGraphic = new int[2]; + StringTokenizer st = new StringTokenizer(node.getNodeValue(), ","); + int index = 0; + while (st.hasMoreTokens() && index < 2) { + Integer value = Integer.parseInt(st.nextToken()); + attackGraphic[index] = value; + index++; + } + attack.setAttackGfx(attackGraphic); + } else if (attribute.equalsIgnoreCase("hitgfx")) { + int[] hitGraphic = new int[2]; + StringTokenizer st = new StringTokenizer(node.getNodeValue(), ","); + int index = 0; + while (st.hasMoreTokens() && index < 2) { + Integer value = Integer.parseInt(st.nextToken()); + hitGraphic[index] = value; + index++; + } + attack.setHitGfx(hitGraphic); + } else if (attribute.equalsIgnoreCase("hit")) { + ArrayList hits = new ArrayList(); + StringTokenizer allHits = new StringTokenizer(node.getNodeValue(), ";"); + while (allHits.hasMoreTokens()) { + StringTokenizer aHit = new StringTokenizer(allHits.nextToken(), ","); + Integer[] hit = new Integer[aHit.countTokens()]; + int index = 0; + while (aHit.hasMoreTokens()) { + Integer value = Integer.parseInt(aHit.nextToken()); + hit[index] = value; + index++; + } + hits.add(hit); + } + int[][] hitz = new int[hits.size()][]; + for (int h = 0; h < hits.size(); h++) { + hitz[h] = new int[hits.get(h).length]; + for (int h2 = 0; h2 < hits.get(h).length; h2++) + hitz[h][h2] = hits.get(h)[h2]; + } + attack.setHits(hitz); + } + } + attacks.add(attack); + } + } + // int health, int[] accuracy, int[] defence, Attack[] attacks, + // int blockAnimation + NpcCombatDef def = new NpcCombatDef(health, attacks.toArray(new Attack[attacks.size()]), blockAnim, deathAnim); + for (int id : npcIds) + list.put(id, def); + } + } catch (Exception e) { + e.printStackTrace(); + } + combatDefs = list; + } +} diff --git a/src/osiris/game/model/dialogues/Dialogue.java b/src/osiris/game/model/dialogues/Dialogue.java new file mode 100644 index 0000000..e274525 --- /dev/null +++ b/src/osiris/game/model/dialogues/Dialogue.java @@ -0,0 +1,182 @@ +package osiris.game.model.dialogues; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.util.ArrayList; +import java.util.List; + +// TODO: Auto-generated Javadoc +/** + * The Class Dialogue. + * + * @author samuraiblood2 + * + */ +public class Dialogue { + + /** The npc. */ + private int npc; + + /** The id. */ + private int id; + + /** The next. */ + private int next; + + /** The title. */ + private String title; + + /** The lines. */ + private List lines = new ArrayList(); + + /** The type. */ + private Type type = Type.NPC; + + /** + * The Enum Type. + */ + public enum Type { + /** The OPTION. */ + OPTION, + + /** The NPC. */ + NPC, + + /** The PLAYER. */ + PLAYER + } + + /** + * Value of. + * + * @param dialogue + * the dialogue + * @return the dialogue + */ + public Dialogue(Dialogue dialogue) { + this(dialogue.getType(), dialogue.getId()); + } + + /** + * Instantiates a new dialogue. + * + * @param type + * the type + * @param id + * the id + */ + public Dialogue(Type type, int id) { + this.type = type; + this.id = id; + } + + /** + * Sets the lines. + * + * @param lines + * the new lines + */ + public void setLines(List lines) { + this.lines.addAll(lines); + } + + /** + * Gets the lines. + * + * @return the lines + */ + public String[] getLines() { + return lines.toArray(new String[0]); + } + + /** + * Sets the title. + * + * @param title + * the new title + */ + public void setTitle(String title) { + this.title = title; + } + + /** + * Gets the title. + * + * @return the title + */ + public String getTitle() { + return title; + } + + /** + * Sets the npc. + * + * @param npc + * the new npc + */ + public void setNpc(int npc) { + this.npc = npc; + } + + /** + * Gets the npc. + * + * @return the npc + */ + public int getNpc() { + return npc; + } + + /** + * Gets the type. + * + * @return the type + */ + public Type getType() { + return type; + } + + /** + * Gets the id. + * + * @return the id + */ + public int getId() { + return id; + } + + /** + * Sets the next. + * + * @param next + * the new next + */ + public void setNext(int next) { + this.next = next; + } + + /** + * Gets the next. + * + * @return the next + */ + public int getNext() { + return next; + } +} \ No newline at end of file diff --git a/src/osiris/game/model/effect/BindingEffect.java b/src/osiris/game/model/effect/BindingEffect.java new file mode 100644 index 0000000..0f84d41 --- /dev/null +++ b/src/osiris/game/model/effect/BindingEffect.java @@ -0,0 +1,87 @@ +package osiris.game.model.effect; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.model.Character; + +// TODO: Auto-generated Javadoc +/** + * The Class BindingEffect. + * + * @author Boomer + */ +public class BindingEffect extends CombatEffect { + + /** The duration. */ + private int duration; + + /** + * Instantiates a new binding effect. + * + * @param cooldown + * the cooldown + * @param attacker + * the attacker + * @param duration + * the duration + */ + public BindingEffect(Integer cooldown, Character attacker, Object duration) { + super(cooldown, attacker); + this.duration = (Integer) duration; + } + + /* + * (non-Javadoc) + * + * @see osiris.game.model.effect.Effect#execute(osiris.game.model.Character) + */ + @Override + public void execute(Character character) { + character.getMovementQueue().reset(); + } + + /* + * (non-Javadoc) + * + * @see + * osiris.game.model.effect.Effect#equals(osiris.game.model.effect.Effect) + */ + @Override + public boolean equals(Effect effect) { + return effect instanceof BindingEffect; + } + + /* + * (non-Javadoc) + * + * @see osiris.game.model.effect.Effect#update(osiris.game.model.Character) + */ + @Override + public void update(Character character) { + } + + /** + * Checks if is binding. + * + * @return true, if is binding + */ + public boolean isBinding() { + return duration >= getTimer().elapsed(); + } +} diff --git a/src/osiris/game/model/effect/BindingSpellEffect.java b/src/osiris/game/model/effect/BindingSpellEffect.java new file mode 100644 index 0000000..67d2601 --- /dev/null +++ b/src/osiris/game/model/effect/BindingSpellEffect.java @@ -0,0 +1,68 @@ +package osiris.game.model.effect; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.model.Character; + +// TODO: Auto-generated Javadoc +/** + * The Class BindingSpellEffect. + * + * @author Boomer + */ +public class BindingSpellEffect extends BindingEffect { + + /** + * Instantiates a new binding spell effect. + * + * @param cooldown + * the cooldown + * @param attacker + * the attacker + * @param duration + * the duration + */ + public BindingSpellEffect(Integer cooldown, Character attacker, Object duration) { + super(cooldown, attacker, duration); + } + + /* + * (non-Javadoc) + * + * @see + * osiris.game.model.effect.BindingEffect#equals(osiris.game.model.effect + * .Effect) + */ + @Override + public boolean equals(Effect effect) { + return effect instanceof BindingSpellEffect; + } + + /* + * (non-Javadoc) + * + * @see + * osiris.game.model.effect.BindingEffect#update(osiris.game.model.Character + * ) + */ + @Override + public void update(Character character) { + } + +} diff --git a/src/osiris/game/model/effect/CombatEffect.java b/src/osiris/game/model/effect/CombatEffect.java new file mode 100644 index 0000000..3540096 --- /dev/null +++ b/src/osiris/game/model/effect/CombatEffect.java @@ -0,0 +1,95 @@ +package osiris.game.model.effect; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.lang.reflect.InvocationTargetException; +import java.util.Arrays; + +import osiris.game.model.Character; + +// TODO: Auto-generated Javadoc +/** + * The Class CombatEffect. + * + * @author Boomer + */ +public abstract class CombatEffect extends ExpiringEffect { + + /** The attacker. */ + private Character attacker; + + /** + * Instantiates a new combat effect. + * + * @param cooldown + * the cooldown + * @param attacker + * the attacker + */ + public CombatEffect(Integer cooldown, Character attacker) { + super(cooldown); + this.attacker = attacker; + } + + /** + * Gets the attacker. + * + * @return the attacker + */ + public Character getAttacker() { + return attacker; + } + + /** + * Creates the. + * + * @param attacker + * the attacker + * @param cooldown + * the cooldown + * @param clazz + * the clazz + * @param params + * the params + * @return the combat effect + */ + public static CombatEffect create(Character attacker, Integer cooldown, Class clazz, Object... params) { + try { + int defaultParams = 2; + Class[] clazzes = new Class[params.length + defaultParams]; + clazzes[0] = Integer.class; + clazzes[1] = Character.class; + Arrays.fill(clazzes, defaultParams, params.length + defaultParams, Object.class); + Object[] objects = new Object[params.length + defaultParams]; + objects[0] = cooldown; + objects[1] = attacker; + System.arraycopy(params, 0, objects, defaultParams, params.length); + return clazz.getConstructor(clazzes).newInstance(objects); + } catch (InstantiationException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } + return null; + } +} diff --git a/src/osiris/game/model/effect/ConsumptionEffect.java b/src/osiris/game/model/effect/ConsumptionEffect.java new file mode 100644 index 0000000..055a25b --- /dev/null +++ b/src/osiris/game/model/effect/ConsumptionEffect.java @@ -0,0 +1,34 @@ +package osiris.game.model.effect; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * The Class ConsumptionEffect. + * + * @author Boomer + */ +public abstract class ConsumptionEffect extends ExpiringEffect { + + /** + * Instantiates a new consumption effect. + */ + public ConsumptionEffect() { + super(4); + } +} diff --git a/src/osiris/game/model/effect/Effect.java b/src/osiris/game/model/effect/Effect.java new file mode 100644 index 0000000..711d987 --- /dev/null +++ b/src/osiris/game/model/effect/Effect.java @@ -0,0 +1,65 @@ +package osiris.game.model.effect; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.model.Character; + +// TODO: Auto-generated Javadoc +/** + * The Class Effect. + * + * @author Boomer + */ +public abstract class Effect { + + /** + * Execute. + * + * @param character + * the character + */ + public abstract void execute(Character character); + + /** + * Equals. + * + * @param effect + * the effect + * @return true, if successful + */ + public abstract boolean equals(Effect effect); + + /** + * Update. + * + * @param character + * the character + */ + public abstract void update(Character character); + + /** + * Terminate. + * + * @param character + * the character + */ + public void terminate(Character character) { + + } +} diff --git a/src/osiris/game/model/effect/ExpiringEffect.java b/src/osiris/game/model/effect/ExpiringEffect.java new file mode 100644 index 0000000..a7c3e4c --- /dev/null +++ b/src/osiris/game/model/effect/ExpiringEffect.java @@ -0,0 +1,94 @@ +package osiris.game.model.effect; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.util.StopWatch; + +// TODO: Auto-generated Javadoc +/** + * The Class ExpiringEffect. + * + * @author Boomer + */ +public abstract class ExpiringEffect extends Effect { + + /** The timer. */ + private StopWatch timer; + + /** The cooldown. */ + private int cooldown; + + /** + * Instantiates a new expiring effect. + * + * @param cooldown + * the cooldown + */ + public ExpiringEffect(Integer cooldown) { + this.cooldown = cooldown; + timer = new StopWatch(); + } + + /** + * Checks for cooled off. + * + * @return true, if successful + */ + public boolean hasCooledOff() { + return cooldown <= timer.elapsed(); + } + + /** + * Gets the timer. + * + * @return the timer + */ + public StopWatch getTimer() { + return timer; + } + + /** + * Cancel. + */ + public void cancel() { + this.cooldown = 0; + } + + /** + * Gets the cooldown. + * + * @return the cooldown + */ + public int getCooldown() { + return cooldown; + } + + /** + * Sets the cooldown. + * + * @param cooldown + * the cooldown + * @return the expiring effect + */ + public ExpiringEffect setCooldown(int cooldown) { + this.cooldown = cooldown; + return this; + } + +} diff --git a/src/osiris/game/model/effect/FoodEffect.java b/src/osiris/game/model/effect/FoodEffect.java new file mode 100644 index 0000000..293f31b --- /dev/null +++ b/src/osiris/game/model/effect/FoodEffect.java @@ -0,0 +1,101 @@ +package osiris.game.model.effect; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.model.Character; +import osiris.game.model.Player; +import osiris.game.model.def.ItemDef; +import osiris.game.model.item.Item; +import osiris.game.update.block.AnimationBlock; + +// TODO: Auto-generated Javadoc +/** + * The Class FoodEffect. + * + * @author Boomer + */ +public class FoodEffect extends ConsumptionEffect { + + /** The healing. */ + private int itemSlot, itemId, healing; + + /** + * Instantiates a new food effect. + * + * @param itemSlot + * the item slot + * @param itemId + * the item id + * @param healing + * the healing + */ + public FoodEffect(int itemSlot, int itemId, int healing) { + super(); + this.itemSlot = itemSlot; + this.itemId = itemId; + this.healing = healing; + } + + /* + * (non-Javadoc) + * + * @see osiris.game.model.effect.Effect#execute(osiris.game.model.Character) + */ + @Override + public void execute(Character character) { + if (character instanceof Player) { + Item item = ((Player) character).getInventory().getItem(itemSlot); + if (item == null || item.getId() != itemId || !((Player) character).getInventory().removeBySlot(itemSlot, 1)) { + cancel(); + return; + } + } + int currentHp = character.getCurrentHp(); + int maxHp = character.getMaxHp(); + int adjustedHp = currentHp + healing; + if (adjustedHp > maxHp) + adjustedHp = maxHp; + character.getCombatManager().adjustAttackDelay(2); + character.setCurrentHp(adjustedHp); + character.addUpdateBlock(new AnimationBlock(character, 829, 0)); + if (character instanceof Player) { + ((Player) character).getEventWriter().sendMessage("You eat the " + ItemDef.forId(itemId).getName() + "."); + } + } + + /* + * (non-Javadoc) + * + * @see + * osiris.game.model.effect.Effect#equals(osiris.game.model.effect.Effect) + */ + @Override + public boolean equals(Effect effect) { + return effect instanceof ConsumptionEffect; + } + + /* + * (non-Javadoc) + * + * @see osiris.game.model.effect.Effect#update(osiris.game.model.Character) + */ + @Override + public void update(Character character) { + } +} diff --git a/src/osiris/game/model/effect/InCombatEffect.java b/src/osiris/game/model/effect/InCombatEffect.java new file mode 100644 index 0000000..517a8e9 --- /dev/null +++ b/src/osiris/game/model/effect/InCombatEffect.java @@ -0,0 +1,72 @@ +package osiris.game.model.effect; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.model.Character; + +// TODO: Auto-generated Javadoc +/** + * The Class InCombatEffect. + * + * @author Boomer + */ +public class InCombatEffect extends CombatEffect { + + /** The Constant COOLDOWN. */ + public static final int COOLDOWN = 8; + + /** + * Instantiates a new in combat effect. + * + * @param attacker + * the attacker + */ + public InCombatEffect(Character attacker) { + super(COOLDOWN, attacker); + } + + /* + * (non-Javadoc) + * + * @see osiris.game.model.effect.Effect#execute(osiris.game.model.Character) + */ + @Override + public void execute(Character character) { + } + + /* + * (non-Javadoc) + * + * @see + * osiris.game.model.effect.Effect#equals(osiris.game.model.effect.Effect) + */ + @Override + public boolean equals(Effect effect) { + return effect instanceof InCombatEffect; + } + + /* + * (non-Javadoc) + * + * @see osiris.game.model.effect.Effect#update(osiris.game.model.Character) + */ + @Override + public void update(Character character) { + } +} diff --git a/src/osiris/game/model/effect/PoisonEffect.java b/src/osiris/game/model/effect/PoisonEffect.java new file mode 100644 index 0000000..7f089ad --- /dev/null +++ b/src/osiris/game/model/effect/PoisonEffect.java @@ -0,0 +1,118 @@ +package osiris.game.model.effect; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.ServerEngine; +import osiris.game.model.Character; +import osiris.game.model.Player; +import osiris.game.model.combat.Hit; +import osiris.game.model.combat.HitType; +import osiris.util.StopWatch; + +// TODO: Auto-generated Javadoc +/** + * The Class PoisonEffect. + * + * @author Boomer + */ +public class PoisonEffect extends CombatEffect { + + /** The damage. */ + double damage; + + /** The timer. */ + private StopWatch timer; + + /** The first hit. */ + private boolean firstHit; + + /** The Constant POISON_INCREMENT. */ + public static final int POISON_INCREMENT = 20; + + /** + * Instantiates a new poison effect. + * + * @param cooldown + * the cooldown + * @param attacker + * the attacker + * @param damage + * the damage + */ + public PoisonEffect(Integer cooldown, Character attacker, Object damage) { + super(((int) ((damage instanceof String ? Double.parseDouble((String) damage) : (Double) damage) / .2) * POISON_INCREMENT) + 5, attacker); + this.damage = (damage instanceof String ? Double.parseDouble((String) damage) : (Double) damage); + this.timer = new StopWatch(); + this.firstHit = false; + } + + /** + * Hit. + * + * @param character + * the character + */ + public void hit(Character character) { + if (damage > 0) { + Hit poison = new Hit(getAttacker(), character, (int) Math.ceil(damage), 0, HitType.POISON); + ServerEngine.getHitQueue().add(poison); + damage -= .2; + } + } + + /* + * (non-Javadoc) + * + * @see osiris.game.model.effect.Effect#execute(osiris.game.model.Character) + */ + @Override + public void execute(Character character) { + if (character instanceof Player) + ((Player) character).getEventWriter().sendMessage("You have been poisoned!"); + } + + /* + * (non-Javadoc) + * + * @see + * osiris.game.model.effect.Effect#equals(osiris.game.model.effect.Effect) + */ + @Override + public boolean equals(Effect effect) { + return effect instanceof PoisonEffect; + } + + /* + * (non-Javadoc) + * + * @see osiris.game.model.effect.Effect#update(osiris.game.model.Character) + */ + @Override + public void update(Character character) { + if (!firstHit && timer.elapsed() >= 3) { + hit(character); + firstHit = true; + timer.reset(); + } + if (timer.elapsed() >= POISON_INCREMENT) { + timer.reset(); + hit(character); + } + } +} diff --git a/src/osiris/game/model/effect/PrayerEffect.java b/src/osiris/game/model/effect/PrayerEffect.java new file mode 100644 index 0000000..9e58031 --- /dev/null +++ b/src/osiris/game/model/effect/PrayerEffect.java @@ -0,0 +1,127 @@ +package osiris.game.model.effect; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.model.Character; +import osiris.game.model.Player; +import osiris.game.model.Skills; +import osiris.game.model.skills.Prayer; +import osiris.util.Utilities; + +// TODO: Auto-generated Javadoc +/** + * The Class PrayerEffect. + * + * @author Boomer + */ +public class PrayerEffect extends Effect { + + /** The prayer. */ + private Prayer prayer; + + /** + * Instantiates a new prayer effect. + * + * @param slot + * the slot + */ + public PrayerEffect(int slot) { + this.prayer = Prayer.values()[slot]; + } + + /** + * Gets the prayer. + * + * @return the prayer + */ + public Prayer getPrayer() { + return prayer; + } + + /* + * (non-Javadoc) + * + * @see osiris.game.model.effect.Effect#execute(osiris.game.model.Character) + */ + @Override + public void execute(Character character) { + if (!(character instanceof Player)) + return; + Player player = (Player) character; + if (prayer == Prayer.PROTECT_FROM_SUMMONING) { + player.getEventWriter().sendMessage("This prayer has not been added yet!"); + player.getEffects().remove(this); + terminate(player); + return; + } + if (player.getSkills().maxLevel(Skills.SKILL_PRAYER) < prayer.getLevelRequired()) { + player.getEventWriter().sendMessage("You need level " + prayer.getLevelRequired() + " to use " + Utilities.toProperCase(prayer.name().replaceAll("_", "")) + "!"); + player.getEffects().remove(this); + terminate(player); + return; + } + + if (player.getSkills().realCurrentLevel(Skills.SKILL_PRAYER) < 1) { + player.getEffects().remove(this); + terminate(player); + return; + } + + if (prayer.getHeadIcon() != -1) + player.setHeadIcon(prayer.getHeadIcon()); + player.getEventWriter().sendConfig(prayer.getConfigId(), 1); + player.getPrayers().put(prayer.getType(), this); + } + + /* + * (non-Javadoc) + * + * @see + * osiris.game.model.effect.Effect#equals(osiris.game.model.effect.Effect) + */ + @Override + public boolean equals(Effect effect) { + return effect instanceof PrayerEffect && ((PrayerEffect) effect).prayer.getType() == prayer.getType(); + } + + /* + * (non-Javadoc) + * + * @see + * osiris.game.model.effect.Effect#terminate(osiris.game.model.Character) + */ + @Override + public void terminate(Character character) { + if (!(character instanceof Player)) + return; + Player player = (Player) character; + if (prayer.getHeadIcon() != -1) + player.setHeadIcon(-1); + player.getEventWriter().sendConfig(prayer.getConfigId(), 0); + } + + /* + * (non-Javadoc) + * + * @see osiris.game.model.effect.Effect#update(osiris.game.model.Character) + */ + @Override + public void update(Character character) { + } +} diff --git a/src/osiris/game/model/effect/StatEffect.java b/src/osiris/game/model/effect/StatEffect.java new file mode 100644 index 0000000..46a2d1e --- /dev/null +++ b/src/osiris/game/model/effect/StatEffect.java @@ -0,0 +1,94 @@ +package osiris.game.model.effect; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.model.Character; +import osiris.game.model.Player; +import osiris.game.model.Skills; +import osiris.util.StopWatch; + +// TODO: Auto-generated Javadoc +/** + * The Class StatEffect. + * + * @author Boomer + */ +@SuppressWarnings("unused") +public class StatEffect extends CombatEffect { + + /** The skill id. */ + private int skillId; + + /** The adjustment. */ + private double adjustment; + + /** The timer. */ + private StopWatch timer; + + /** + * Instantiates a new stat effect. + * + * @param cooldown + * the cooldown + * @param character + * the character + * @param skillId + * the skill id + * @param adjustment + * the adjustment + */ + public StatEffect(Integer cooldown, Character character, Object skillId, Object adjustment) { + super(cooldown, null); + this.skillId = (Integer) skillId; + this.adjustment = (Double) adjustment; + } + + /* + * (non-Javadoc) + * + * @see osiris.game.model.effect.Effect#execute(osiris.game.model.Character) + */ + @Override + public void execute(Character character) { + if (character instanceof Player) { + Skills skills = ((Player) character).getSkills(); + skills.setCurLevel(skillId, skills.currentLevel(skillId) + (int) (skills.maxLevel(skillId) * adjustment)); + } + } + + /* + * (non-Javadoc) + * + * @see + * osiris.game.model.effect.Effect#equals(osiris.game.model.effect.Effect) + */ + @Override + public boolean equals(Effect effect) { + return effect instanceof StatEffect && ((StatEffect) effect).skillId == skillId; + } + + /* + * (non-Javadoc) + * + * @see osiris.game.model.effect.Effect#update(osiris.game.model.Character) + */ + @Override + public void update(Character character) { + } +} diff --git a/src/osiris/game/model/ground/GroundItem.java b/src/osiris/game/model/ground/GroundItem.java new file mode 100644 index 0000000..caab120 --- /dev/null +++ b/src/osiris/game/model/ground/GroundItem.java @@ -0,0 +1,227 @@ +package osiris.game.model.ground; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.Main; +import osiris.game.model.Character; +import osiris.game.model.Player; +import osiris.game.model.Position; +import osiris.game.model.Viewable; +import osiris.game.model.item.Item; +import osiris.util.StopWatch; + +// TODO: Auto-generated Javadoc +/** + * The Class GroundItem. + * + * @author Boomer + */ +public class GroundItem extends Viewable { + + /** The item. */ + private Item item; + + /** The view first. */ + private DropCharacter dropper = null, viewFirst = null; + + /** The timer. */ + private StopWatch timer; + + /** The stage. */ + private GroundStage stage = null; + + /** + * The Enum GroundStage. + */ + public enum GroundStage { + + /** The PRIVATE. */ + PRIVATE, + + /** The PUBLIC. */ + PUBLIC, + + /** The HIDDEN. */ + HIDDEN + } + + /* Use for respawning world items */ + /** + * Instantiates a new ground item. + * + * @param item + * the item + * @param position + * the position + */ + public GroundItem(Item item, Position position) { + this.item = item; + this.setPosition(position); + this.timer = new StopWatch(); + this.stage = GroundStage.PUBLIC; + } + + /** + * Instantiates a new ground item. + * + * @param item + * the item + * @param dropper + * the dropper + * @param position + * the position + */ + public GroundItem(Item item, Character dropper, Position position) { + this(item, position); + this.dropper = new DropCharacter(dropper); + this.stage = GroundStage.PRIVATE; + this.viewFirst = this.dropper; + Character views = viewFirst.getCharacter(); + } + + /* Use for dropping an item */ + /** + * Instantiates a new ground item. + * + * @param item + * the item + * @param dropper + * the dropper + */ + public GroundItem(Item item, Character dropper) { + this(item, dropper, dropper.getPosition()); + } + + /* Use for kill drops */ + /** + * Instantiates a new ground item. + * + * @param item + * the item + * @param dropper + * the dropper + * @param viewFirst + * the view first + * @param position + * the position + */ + public GroundItem(Item item, Character dropper, Character viewFirst, Position position) { + this(item, dropper, position); + this.viewFirst = new DropCharacter(viewFirst); + Character views = this.viewFirst.getCharacter(); + } + + /** + * Gets the dropper. + * + * @return the dropper + */ + public Character getDropper() { + if (dropper == null) + return null; + return dropper.getCharacter(); + } + + /** + * Gets the view first. + * + * @return the view first + */ + public Character getViewFirst() { + if (viewFirst == null) + return null; + return viewFirst.getCharacter(); + } + + /** + * Gets the item. + * + * @return the item + */ + public Item getItem() { + return item; + } + + /** + * Gets the timer. + * + * @return the timer + */ + public StopWatch getTimer() { + return timer; + } + + /** + * Gets the stage. + * + * @return the stage + */ + public GroundStage getStage() { + return stage; + } + + /** + * Sets the stage. + * + * @param stage + * the new stage + */ + public void setStage(GroundStage stage) { + this.stage = stage; + } + + /** + * The Class DropCharacter. + */ + class DropCharacter { + + /** The player. */ + boolean player; + + /** The unique id. */ + int uniqueId = -1; + + /** + * Instantiates a new drop character. + * + * @param dropper + * the dropper + */ + public DropCharacter(Character dropper) { + if (dropper != null) { + this.player = dropper instanceof Player; + this.uniqueId = player ? ((Player) dropper).getUniqueId() : dropper.getSlot(); + } + } + + /** + * Gets the character. + * + * @return the character + */ + public Character getCharacter() { + if (uniqueId == -1) + return null; + if (player) + return Main.findPlayer(uniqueId); + else + return Main.getNpcs().get(uniqueId); + } + } +} diff --git a/src/osiris/game/model/ground/GroundManager.java b/src/osiris/game/model/ground/GroundManager.java new file mode 100644 index 0000000..209186a --- /dev/null +++ b/src/osiris/game/model/ground/GroundManager.java @@ -0,0 +1,286 @@ +package osiris.game.model.ground; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.util.Iterator; +import java.util.LinkedList; + +import osiris.Main; +import osiris.game.model.Character; +import osiris.game.model.Npc; +import osiris.game.model.Player; +import osiris.game.model.Position; +import osiris.game.model.def.ItemDef; + +// TODO: Auto-generated Javadoc +/** + * The Class GroundManager. + * + * @author Boomer + */ +public class GroundManager implements Runnable { + + /** The ground items. */ + private LinkedList groundItems = new LinkedList(); + + /* + * (non-Javadoc) + * + * @see java.lang.Runnable#run() + */ + @Override + public void run() { + try { + for (Iterator items = groundItems.iterator(); items.hasNext();) { + GroundItem item = items.next(); + if (item.getTimer().elapsed() >= 60) { + boolean tradeable = ItemDef.forId(item.getItem().getId()).isTradeable(); + switch (item.getStage()) { + case PRIVATE: + if (!tradeable) { + items.remove(); + item.setStage(GroundItem.GroundStage.HIDDEN); + } else + item.setStage(GroundItem.GroundStage.PUBLIC); + updateItem(item); + break; + case HIDDEN: + item.setStage(GroundItem.GroundStage.PUBLIC); + updateItem(item); + break; + case PUBLIC: + if (item.getItem().respawns()) + break; + else + items.remove(); + item.setStage(GroundItem.GroundStage.HIDDEN); + updateItem(item); + break; + } + item.getTimer().reset(); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * Refresh landscape display. + * + * @param player + * the player + */ + public void refreshLandscapeDisplay(Player player) { + for (GroundItem item : player.getGroundItems()) + player.getEventWriter().sendDestroyGroundItem(item.getItem(), item.getPosition()); + player.getGroundItems().clear(); + displayGroundItems(player, groundItems); + } + + /** + * Refresh position display. + * + * @param player + * the player + * @param position + * the position + */ + public void refreshPositionDisplay(Player player, Position position) { + LinkedList tileItems = new LinkedList(); + for (Iterator items = player.getGroundItems().iterator(); items.hasNext();) { + GroundItem item = items.next(); + if (item.getPosition().equals(position)) { + if (groundItems.contains(item)) + tileItems.add(item); + player.getEventWriter().sendDestroyGroundItem(item.getItem(), item.getPosition()); + items.remove(); + } + } + displayGroundItems(player, tileItems); + } + + /** + * Display ground items. + * + * @param player + * the player + * @param items + * the items + */ + public void displayGroundItems(Player player, LinkedList items) { + for (GroundItem item : items) + displayGroundItem(player, item); + } + + /** + * Display ground item. + * + * @param player + * the player + * @param item + * the item + */ + public void displayGroundItem(Player player, GroundItem item) { + if (canSeeItem(player, item)) { + player.getEventWriter().sendCreateGroundItem(item.getItem(), item.getPosition()); + player.getGroundItems().add(item); + } + } + + /** + * Update item. + * + * @param item + * the item + */ + public void updateItem(GroundItem item) { + Character viewFirst = item.getViewFirst(); + if (item.getStage() == GroundItem.GroundStage.PRIVATE) { + if (viewFirst != null && viewFirst instanceof Player) { + mergeItems(item, viewFirst); + if (viewFirst.isInSight(item.getPosition(), 32)) { + ((Player) viewFirst).getGroundItems().add(item); + refreshPositionDisplay(((Player) viewFirst), item.getPosition()); + } + } + } else if (item.getStage() == GroundItem.GroundStage.PUBLIC) { + if (viewFirst != null && viewFirst instanceof Player) + ((Player) viewFirst).getGroundItems().remove(item); + mergeItems(item, viewFirst); + for (Player player : Main.getPlayers()) + if (player.isInSight(item.getPosition(), 32)) { + player.getGroundItems().add(item); + refreshPositionDisplay(player, item.getPosition()); + } + } else if (item.getStage() == GroundItem.GroundStage.HIDDEN) { + for (Player player : Main.getPlayers()) + refreshPositionDisplay(player, item.getPosition()); + } + } + + /** + * Merge items. + * + * @param item + * the item + * @param viewFirst + * the view first + */ + public void mergeItems(GroundItem item, Character viewFirst) { + if (ItemDef.forId(item.getItem().getId()).isStackable()) { + // MERGING + for (Iterator items = groundItems.iterator(); items.hasNext();) { + GroundItem other = items.next(); + Character otherViewFirst = other.getViewFirst(); + if (!item.equals(other) && item.getItem().getId() == other.getItem().getId() && other.getPosition().equals(item.getPosition()) && otherViewFirst != null && otherViewFirst instanceof Player && canSeeItem((Player) otherViewFirst, item) && viewFirst != null && viewFirst instanceof Player && canSeeItem((Player) viewFirst, other)) { + item.getItem().adjustAmount(other.getItem().getAmount()); + item.getTimer().reset(); + items.remove(); + break; + } + } + // END OF MERGING + } + } + + /** + * Drop item. + * + * @param item + * the item + */ + public void dropItem(GroundItem item) { + groundItems.add(item); + updateItem(item); + } + + /** + * Pickup item. + * + * @param item + * the item + */ + public void pickupItem(GroundItem item) { + item.setStage(GroundItem.GroundStage.HIDDEN); + updateItem(item); + if (!item.getItem().respawns()) + groundItems.remove(item); + } + + /** + * Can see item. + * + * @param player + * the player + * @param item + * the item + * @return true, if successful + */ + public boolean canSeeItem(Player player, GroundItem item) { + if (item.getStage() == GroundItem.GroundStage.HIDDEN) + return false; + ItemDef def = ItemDef.forId(item.getItem().getId()); + Character viewFirst = item.getViewFirst(); + Character dropper = item.getDropper(); + if (!def.isTradeable()) { + if (dropper != null) { + if (dropper instanceof Npc) + return viewFirst != null && viewFirst.equals(player); + else + return dropper.equals(player); + } else + return false; + } else + return (item.isInSight(player) && ((item.getStage() == GroundItem.GroundStage.PUBLIC || (item.getStage() == GroundItem.GroundStage.PRIVATE && (viewFirst == null || viewFirst.equals(player)))))); + } + + /** + * Display empty landscape. + * + * @param player + * the player + */ + public void displayEmptyLandscape(Player player) { + for (GroundItem item : player.getGroundItems()) + player.getEventWriter().sendDestroyGroundItem(item.getItem(), item.getPosition()); + } + + /** The manager. */ + private static GroundManager manager; + + /** + * Creates the. + * + * @return the ground manager + */ + public static GroundManager create() { + manager = new GroundManager(); + return manager; + } + + /** + * Gets the manager. + * + * @return the manager + */ + public static GroundManager getManager() { + return manager; + } +} diff --git a/src/osiris/game/model/item/Bank.java b/src/osiris/game/model/item/Bank.java new file mode 100644 index 0000000..36c7f6b --- /dev/null +++ b/src/osiris/game/model/item/Bank.java @@ -0,0 +1,324 @@ +package osiris.game.model.item; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.util.ArrayList; + +import osiris.game.model.Player; +import osiris.game.model.def.ItemDef; +import osiris.util.BankUtilities; +import osiris.util.Utilities; + +// TODO: Auto-generated Javadoc +/** + * The Class Bank. + * + * @author Boomer + * + */ +public class Bank { + + /** + * The main. + */ + private final ItemContainer main; + + /** + * The tabs. + */ + private final ArrayList tabs; + + /** The bank open. */ + private boolean bankOpen; + + /** + * The player. + */ + private final Player player; + + /** + * The viewing tab. + */ + private int viewingTab = -1; + + /** + * The withdraw notes. + */ + private boolean withdrawNotes = false; + + /** + * Instantiates a new bank. + * + * @param player + * the player + */ + public Bank(Player player) { + this.tabs = new ArrayList(); + main = getBlankTab(player); + this.player = player; + this.bankOpen = false; + } + + /** + * Withdraw notes. + * + * @return true, if successful + */ + public boolean withdrawNotes() { + return withdrawNotes; + } + + /** + * Switch withdraw notes. + */ + public void switchWithdrawNotes() { + withdrawNotes = !withdrawNotes; + } + + /** + * Adds the. + * + * @param itemId + * the item id + * @param itemN + * the item n + */ + public void add(int itemId, int itemN) { + int slotOfItem = main.getSlotById(itemId); + int tabOfItem = -1; + if (slotOfItem == -1) + for (int i = 0; i < tabs.size(); i++) { + if (slotOfItem == -1) { + slotOfItem = tabs.get(i).getSlotById(itemId); + tabOfItem = i; + } + } + if (slotOfItem != -1) { + if (tabOfItem == -1) + main.add(itemId, itemN); + else + tabs.get(tabOfItem).add(itemId, itemN); + } else { + if (tabs.size() == 0) + main.add(itemId, itemN); + else { + if (viewingTab == -1) + main.add(itemId, itemN); + else + tabs.get(viewingTab).add(itemId, itemN); + } + } + refresh(); + } + + /** + * Change item tab. + * + * @param slotFrom + * the slot from + * @param toTabId + * the to tab id + * @param slotTo + * the slot to + */ + public void changeItemTab(int slotFrom, int toTabId, int slotTo) { + + int tabSlotFrom = this.getTabBySlot(slotFrom); + int tabSlotTo = BankUtilities.tabIdToContainerSlot(toTabId); + if (getItems().length < slotFrom) + return; + if (tabSlotFrom == tabSlotTo) { + ItemContainer container = main; + if (tabSlotFrom != -1) + container = tabs.get(tabSlotFrom); + container.move(slotFrom, slotTo); + } + refresh(); + } + + /** + * Removes the. + * + * @param slotFrom + * the slot from + * @param amount + * the amount + * @return true, if successful + */ + public boolean remove(int slotFrom, int amount) { + Item item = getItems()[slotFrom]; + int totalAmount = item.getAmount(); + int tabSlotFrom = this.getTabBySlot(slotFrom); + int id = item.getId(); + boolean removed; + if (totalAmount < amount) + amount = totalAmount; + if (tabSlotFrom == -1) + removed = main.removeById(id, amount); + else + removed = tabs.get(tabSlotFrom).removeById(id, amount); + cleanup(); + refresh(); + return removed; + } + + /** + * Gets the main. + * + * @return the main + */ + public ItemContainer getMain() { + return main; + } + + /** + * Gets the tabs. + * + * @return the tabs + */ + public ArrayList getTabs() { + return tabs; + } + + /** + * Refresh. + */ + public void refresh() { + for (ContainerDef def : main.getDefs()) + player.getEventWriter().sendItems(def.interfaceId, def.childId, def.type, getItems()); + player.getEventWriter().sendString(Utilities.toProperCase(player.getUsername()) + "'s Bank", 762, 24); + player.getEventWriter().sendString("" + main.countFilledSlots(), 762, 97); + player.getEventWriter().sendString("" + main.getLength(), 762, 98); + // BankUtilities.sendBankTabConfig(player); + } + + /** + * Gets the items. + * + * @return the items + */ + public Item[] getItems() { + ArrayList items = new ArrayList(); + for (int i = 0; i < tabs.size(); i++) + for (Item item : tabs.get(i).getItems()) + if (item != null) + items.add(item); + for (Item item : main.getItems()) + if (item != null) + items.add(item); + return items.toArray(new Item[items.size()]); + } + + /** + * Can fit. + * + * @param itemId + * the item id + * @param itemN + * the item n + * @return true, if successful + */ + public boolean canFit(int itemId, int itemN) { + int slotOfItem = getSlotOfItem(itemId); + if (slotOfItem == -1) + return getItems().length < BankUtilities.BANK_SIZE; + else { + int tab = this.getTabBySlot(slotOfItem); + ItemContainer container = main; + if (tab != -1) + container = tabs.get(tab); + Item item = container.getItem(slotOfItem); + return item != null && ((Integer.MAX_VALUE - item.getAmount()) > itemN); + } + } + + /** + * Gets the slot of item. + * + * @param itemId + * the item id + * @return the slot of item + */ + public int getSlotOfItem(int itemId) { + for (int i = 0; i < getItems().length; i++) + if (getItems()[i].getId() == itemId || (ItemDef.forId(itemId).isNoted() && (itemId - 1) == getItems()[i].getId())) + return i; + return -1; + } + + /** + * Gets the tab by slot. + * + * @param slot + * the slot + * @return the tab by slot + */ + public int getTabBySlot(int slot) { + int filledSlots = 0; + for (int i = 0; i < tabs.size(); i++) { + filledSlots += tabs.get(i).countFilledSlots(); + if (slot < filledSlots) + return i; + } + return -1; + } + + /** + * Cleanup. + */ + public void cleanup() { + ArrayList temp = new ArrayList(); + for (ItemContainer container : tabs) + if (container != null && container.countFilledSlots() > 0) + temp.add(container); + tabs.clear(); + for (int i = 0; i < temp.size(); i++) + tabs.add(i, temp.get(i)); + } + + /** + * Checks if is bank open. + * + * @return true, if is bank open + */ + public boolean isBankOpen() { + return bankOpen; + } + + /** + * Sets the bank open. + * + * @param open + * the new bank open + */ + public void setBankOpen(boolean open) { + this.bankOpen = open; + } + + /** + * Gets the blank tab. + * + * @param player + * the player + * @return the blank tab + */ + public static final ItemContainer getBlankTab(Player player) { + return new ItemContainer(BankUtilities.BANK_SIZE, true, true, false, new ContainerDef[] { new ContainerDef(-1, 64207, 95) }, player); + } +} \ No newline at end of file diff --git a/src/osiris/game/model/item/ContainerDef.java b/src/osiris/game/model/item/ContainerDef.java new file mode 100644 index 0000000..20e6f37 --- /dev/null +++ b/src/osiris/game/model/item/ContainerDef.java @@ -0,0 +1,79 @@ +package osiris.game.model.item; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +// TODO: Auto-generated Javadoc + +/** + * The Class ContainerDef. + * + * @author Boomer + * + */ +public class ContainerDef { + + /** + * The type. + */ + final int interfaceId, childId, type; + + /** + * Instantiates a new container def. + * + * @param interfaceId + * the interface id + * @param childId + * the child id + * @param type + * the type + */ + public ContainerDef(int interfaceId, int childId, int type) { + this.interfaceId = interfaceId; + this.childId = childId; + this.type = type; + } + + /** + * Gets the interface id. + * + * @return the interface id + */ + public final int getInterfaceId() { + return interfaceId; + } + + /** + * Gets the child id. + * + * @return the child id + */ + public int getChildId() { + return childId; + } + + /** + * Gets the type. + * + * @return the type + */ + public int getType() { + return type; + } + +} diff --git a/src/osiris/game/model/item/Item.java b/src/osiris/game/model/item/Item.java new file mode 100644 index 0000000..bf83918 --- /dev/null +++ b/src/osiris/game/model/item/Item.java @@ -0,0 +1,188 @@ +package osiris.game.model.item; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * The Class Item. + * + * @author Boomer + * + */ +public class Item { + + /** + * The item n. + */ + private int itemId; + + /** + * The item n. + */ + private int itemN; + + /** + * The respawns. + */ + private boolean respawns; + + /** + * Instantiates a new item. + * + * @param itemId + * the item id + */ + public Item(int itemId) { + this.itemId = itemId; + this.itemN = 1; + } + + /** + * Instantiates a new item. + * + * @param itemId + * the item id + * @param itemN + * the item n + */ + public Item(int itemId, int itemN) { + this.itemId = itemId; + this.itemN = itemN; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "Item[id=" + itemId + " amount=" + itemN + "]"; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#clone() + */ + @Override + public Item clone() { + return new Item(itemId, itemN); + } + + /** + * Sets the respawns. + * + * @param respawns + * the respawns + * @return the item + */ + public Item setRespawns(boolean respawns) { + this.respawns = respawns; + return this; + } + + /** + * Gets the id. + * + * @return the id + */ + public int getId() { + return itemId; + } + + /** + * Gets the amt. + * + * @return the amt + */ + public int getAmount() { + return itemN; + } + + /** + * Respawns. + * + * @return true, if successful + */ + public boolean respawns() { + return respawns; + } + + /** + * Sets the amount. + * + * @param itemN + * the new amount + */ + public void setAmount(int itemN) { + this.itemN = itemN; + } + + /** + * Sets the id. + * + * @param id + * the new id + */ + public void setId(int id) { + this.itemId = id; + } + + /** + * Adjust amount. + * + * @param amount + * the amount + * @return true, if successful + */ + public boolean adjustAmount(int amount) { + if (amount < 0 && Math.abs(amount) > itemN) + return false; + else if (amount > 0 && (Integer.MAX_VALUE - itemN) < amount) + itemN = Integer.MAX_VALUE; + else + this.itemN += amount; + return true; + } + + /** + * Creates the. + * + * @param itemId + * the item id + * @return the item + */ + public static Item create(int itemId) { + return new Item(itemId); + } + + /** + * Creates the. + * + * @param itemId + * the item id + * @param itemN + * the item n + * @return the item + */ + public static Item create(int itemId, int itemN) { + return new Item(itemId, itemN); + } + +} \ No newline at end of file diff --git a/src/osiris/game/model/item/ItemContainer.java b/src/osiris/game/model/item/ItemContainer.java new file mode 100644 index 0000000..7be00d4 --- /dev/null +++ b/src/osiris/game/model/item/ItemContainer.java @@ -0,0 +1,604 @@ +package osiris.game.model.item; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; + +import osiris.game.model.Player; +import osiris.game.model.def.ItemDef; + +// TODO: Auto-generated Javadoc +/** + * inventory, bank, equipment, shops, trades, duels, deposit boxes, etc. + * + * @author Boomer + * + */ +public class ItemContainer { + + /** The items. */ + private Item[] items; + + /** The length. */ + private final int length; + + /** The allows notes. */ + private final boolean slides, forceStacks, allowsNotes; + + /** The players. */ + private final ArrayList players; + + /** The defs. */ + private final ContainerDef[] defs; + + /** + * Instantiates a new item container. + * + * @param length + * the length + * @param slides + * the slides + * @param forceStacks + * the force stacks + * @param allowsNotes + * the allows notes + * @param defs + * the defs + * @param players + * the players + */ + public ItemContainer(final int length, final boolean slides, final boolean forceStacks, boolean allowsNotes, ContainerDef[] defs, Player... players) { + this.length = length; + this.items = new Item[length]; + this.slides = slides; + this.forceStacks = forceStacks; + this.allowsNotes = allowsNotes; + this.defs = defs; + this.players = new ArrayList(Arrays.asList(players)); + + } + + /** + * Gets the items. + * + * @return the items + */ + public Item[] getItems() { + return items; + } + + /** + * Sets the. + * + * @param slot + * the slot + * @param item + * the item + * @param refresh + * the refresh + */ + public void set(int slot, Item item, boolean refresh) { + try { + if (item != null) { + if (ItemDef.forId(item.getId()).isNoted() && !this.allowsNotes) + item.setId(item.getId() - 1); + } + items[slot] = item; + if (refresh) + refresh(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * Sets the. + * + * @param slot + * the slot + * @param item + * the item + */ + public void set(int slot, Item item) { + set(slot, item, true); + } + + /** + * Adds the. + * + * @param itemId + * the item id + * @param itemN + * the item n + * @return true, if successful + */ + public boolean add(int itemId, int itemN) { + return add(Item.create(itemId, itemN)); + } + + /** + * Adds the. + * + * @param item + * the item + * @return true, if successful + */ + public boolean add(Item item) { + if (item == null) + return true; + int itemId = item.getId(); + int itemN = item.getAmount(); + ItemDef itemDef = ItemDef.forId(itemId); + boolean stackable = itemDef.isStackable(); + if (!allowsNotes && itemDef.isNoted()) + itemId -= 1; + if (forceStacks || stackable) { + int slotOfItem = getSlotById(itemId); + if (slotOfItem == -1) { + int emptySlot = getEmptySlot(); + if (emptySlot == -1) + return false; // No space in container + else + items[emptySlot] = Item.create(itemId, itemN); + refresh(); + return true; // Added to new blank slot + } else { + boolean added = items[slotOfItem].adjustAmount(itemN); + if (added) + refresh(); + return added; + } + } else { + ArrayList emptySlots = getEmptySlots(); + if (emptySlots.size() < itemN) + return false; + for (int i = 0; i < itemN; i++) + items[emptySlots.get(i)] = Item.create(itemId); + refresh(); + return true; // Added to new blank slot + } + } + + /** + * Removes the by id. + * + * @param itemId + * the item id + * @param itemN + * the item n + * @return true, if successful + */ + public boolean removeById(int itemId, int itemN) { + return removeById(itemId, itemN, true); + } + + /** + * Removes the by id. + * + * @param itemId + * the item id + * @param itemN + * the item n + * @param refresh + * the refresh + * @return true, if successful + */ + public boolean removeById(int itemId, int itemN, boolean refresh) { + int slot = this.getSlotById(itemId); + return slot != -1 && removeBySlot(slot, itemN, refresh); + } + + /** + * Removes the by slot. + * + * @param slot + * the slot + * @param itemN + * the item n + * @return true, if successful + */ + public boolean removeBySlot(int slot, int itemN) { + return removeBySlot(slot, itemN, true); + } + + /** + * Removes the by slot. + * + * @param slot + * the slot + * @param itemN + * the item n + * @param refresh + * the refresh + * @return true, if successful + */ + public boolean removeBySlot(int slot, int itemN, boolean refresh) { + try { + Item item = items[slot]; + if (item == null) + return false; + boolean stackable = ItemDef.forId(item.getId()).isStackable(); + if (forceStacks || stackable) { + boolean removed = item.adjustAmount(-itemN); + if (item.getAmount() == 0 && !item.respawns()) { + items[slot] = null; + if (slides) + slide(); + } + if (removed && refresh) + refresh(); + return removed; + } else if (itemN == 1) { + items[slot] = null; + if (slides) + slide(); + if (refresh) + refresh(); + return true; + } else { + HashMap commonItems = getAllItemsById(item.getId()); + Integer[] common = commonItems.keySet().toArray(new Integer[commonItems.keySet().size()]); + if (common.length >= itemN) { + for (int i = 0; i < itemN; i++) { + int itemSlot = common[i]; + if (items[itemSlot].respawns()) { + items[itemSlot].setAmount(0); + } else + items[itemSlot] = null; + } + if (slides) + slide(); + if (refresh) + refresh(); + return true; + } else + return false; + } + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } + + /** + * Can fit. + * + * @param itemId + * the item id + * @param itemN + * the item n + * @return true, if successful + */ + public boolean canFit(int itemId, int itemN) { + if (forceStacks || ItemDef.forId(itemId).isStackable()) { + return this.getSlotById(itemId) != -1 || this.countEmptySlots() > 0; + } else + return this.countEmptySlots() > itemN; + } + + /** + * Checks if is full. + * + * @return true, if is full + */ + public boolean isFull() { + return countEmptySlots() == 0; + } + + /** + * Slide. + */ + public void slide() { + ArrayList temp = new ArrayList(); + for (Item item : items) + if (item != null) + temp.add(item); + clear(); + System.arraycopy(temp.toArray(new Item[temp.size()]), 0, items, 0, temp.size()); + } + + /** + * Clear. + */ + public void clear() { + items = new Item[length]; + } + + /** + * Empty. + */ + public void empty() { + clear(); + refresh(); + } + + /** + * Gets the empty slot. + * + * @return the empty slot + */ + public int getEmptySlot() { + for (int i = 0; i < items.length; i++) + if (items[i] == null) + return i; + return -1; + } + + /** + * Count empty slots. + * + * @return the int + */ + public int countEmptySlots() { + int count = 0; + for (Item item : items) + if (item == null) + count++; + return count; + } + + /** + * Gets the empty slots. + * + * @return the empty slots + */ + public ArrayList getEmptySlots() { + ArrayList temp = new ArrayList(); + for (int i = 0; i < items.length; i++) + if (items[i] == null) + temp.add(i); + return temp; + } + + /** + * Count filled slots. + * + * @return the int + */ + public int countFilledSlots() { + int count = 0; + for (Item item : items) + if (item != null) + count++; + return count; + } + + /** + * Amount of item. + * + * @param itemId + * the item id + * @return the int + */ + public int amountOfItem(int itemId) { + int count = 0; + for (Item item : items) + if (item != null && item.getId() == itemId) + count += item.getAmount(); + return count; + } + + /** + * Gets the slot by id. + * + * @param itemId + * the item id + * @return the slot by id + */ + public int getSlotById(int itemId) { + for (int i = 0; i < items.length; i++) + if (items[i] != null && items[i].getId() == itemId) + return i; + return -1; + } + + /** + * Gets the item. + * + * @param slot + * the slot + * @return the item + */ + public Item getItem(int slot) { + try { + return items[slot]; + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + /** + * Gets the item by id. + * + * @param itemId + * the item id + * @return the item by id + */ + public Item getItemById(int itemId) { + for (Item item : items) + if (item != null && item.getId() == itemId) + return item; + return null; + } + + /** + * Gets the all items by id. + * + * @param itemId + * the item id + * @return the all items by id + */ + public HashMap getAllItemsById(int itemId) { + HashMap temp = new HashMap(); + for (int i = 0; i < items.length; i++) + if (items[i] != null && items[i].getId() == itemId) + temp.put(i, items[i]); + return temp; + } + + /** + * Gets the item by name. + * + * @param name + * the name + * @return the item by name + */ + public Item getItemByName(String name) { + for (Item item : items) + if (item != null && ItemDef.forId(item.getId()).getName().toLowerCase().contains(name.toLowerCase())) + return item; + return null; + } + + /** + * Gets the all items by name. + * + * @param name + * the name + * @return the all items by name + */ + public HashMap getAllItemsByName(String name) { + HashMap temp = new HashMap(); + for (int i = 0; i < items.length; i++) + if (items[i] != null && ItemDef.forId(items[i].getId()).getName().toLowerCase().contains(name.toLowerCase())) + temp.put(i, items[i]); + return temp; + } + + /** + * Move. + * + * @param slotFrom + * the slot from + * @param slotTo + * the slot to + */ + public void move(int slotFrom, int slotTo) { + Item from = items[slotFrom]; + Item to = items[slotTo]; + items[slotFrom] = to; + items[slotTo] = from; + refresh(); + } + + /** + * Amount can hold. + * + * @param itemId + * the item id + * @return the int + */ + public int amountCanHold(int itemId) { + ItemDef def = ItemDef.forId(itemId); + ArrayList emptySlots = this.getEmptySlots(); + if ((forceStacks || ItemDef.forId(itemId).isStackable()) && this.amountOfItem(itemId) > 0) + return Integer.MAX_VALUE; + else if (emptySlots.size() == 0) + return 0; + if (def.isStackable()) + return Integer.MAX_VALUE; + else + return emptySlots.size(); + } + + /** + * Refresh. + */ + public void refresh() { + for (Player player : players) + for (ContainerDef def : defs) + player.getEventWriter().sendItems(def.interfaceId, def.childId, def.type, this); + } + + /** + * Gets the defs. + * + * @return the defs + */ + public ContainerDef[] getDefs() { + return defs; + } + + /** + * Gets the length. + * + * @return the length + */ + public int getLength() { + return length; + } + + /** + * Sets the items. + * + * @param items + * the new items + */ + public void setItems(Item[] items) { + this.items = items; + } + + /** + * Can fit all. + * + * @param items + * the items + * @return true, if successful + */ + public boolean canFitAll(Item[] items) { + int slotsRequired = slotsRequired(items); + return this.countEmptySlots() >= slotsRequired; + } + + /** + * Slots required. + * + * @param items + * the items + * @return the int + */ + public int slotsRequired(Item[] items) { + int slotsRequired = 0; + for (Item item : items) { + if (item == null) + continue; + boolean stackable = ItemDef.forId(item.getId()).isStackable(); + if (item != null && (!stackable || this.getSlotById(item.getId()) != -1)) { + if (!stackable) + slotsRequired += item.getAmount(); + else + slotsRequired++; + } + } + return slotsRequired; + + } + + /** + * Gets the players. + * + * @return the players + */ + public ArrayList getPlayers() { + return players; + } +} diff --git a/src/osiris/game/model/item/Trade.java b/src/osiris/game/model/item/Trade.java new file mode 100644 index 0000000..fbd38fd --- /dev/null +++ b/src/osiris/game/model/item/Trade.java @@ -0,0 +1,352 @@ +package osiris.game.model.item; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.action.Action; +import osiris.game.action.impl.TradeAction; +import osiris.game.model.Player; +import osiris.game.model.def.ItemDef; + +// TODO: Auto-generated Javadoc +/** + * The Class Trade. + * + * @author Boomer + */ +@SuppressWarnings("unused") +public class Trade { + + /** The exchange stage. */ + private boolean tradeOpen, exchangeStage; + + /** The players. */ + private Player[] players; + + /** The containers. */ + private ItemContainer[][] containers; + + /** The player accepted. */ + private Player playerAccepted; + + /** The Constant TRADE_INTERFACE. */ + public static final ContainerDef[] TRADE_INTERFACE = { new ContainerDef(-1, 2, 90), new ContainerDef(-2, 60981, 90) }; + + /** + * Instantiates a new trade. + * + * @param players + * the players + */ + public Trade(Player[] players) { + this.players = players; + containers = new ItemContainer[players.length][2]; + playerAccepted = null; + for (int i = 0; i < players.length; i++) { + containers[i][0] = new ItemContainer(28, true, false, true, new ContainerDef[] { TRADE_INTERFACE[0] }, players[i]); + containers[i][1] = new ItemContainer(28, true, false, true, new ContainerDef[] { TRADE_INTERFACE[1] }, players[i]); + } + } + + /** + * Open trade. + * + * @param player + * the player + */ + public void openTrade(Player player) { + player.getEventWriter().openSideLockingInterface(335, 336); + player.getEventWriter().setAccessMask(1026, 30, 335, 0, 27); + player.getEventWriter().setAccessMask(1026, 32, 335, 0, 27); + player.getEventWriter().setAccessMask(1278, 0, 336, 0, 27); + Object[] tparams1 = new Object[] { "", "", "", "Value", "Remove-X", "Remove-All", "Remove-10", "Remove-5", "Remove", -1, 0, 7, 4, 90, 21954590 }; + Object[] tparams2 = new Object[] { "", "", /* Lend */"", "Value", "Offer-X", "Offer-All", "Offer-10", "Offer-5", "Offer", -1, 0, 7, 4, 93, 22020096 }; + Object[] tparams3 = new Object[] { "", "", "", "", "", "", "", "", "Value", -1, 0, 7, 4, 90, 21954592 }; + player.getEventWriter().runScript(150, tparams1, "IviiiIsssssssss"); + player.getEventWriter().runScript(150, tparams2, "IviiiIsssssssss"); + player.getEventWriter().runScript(695, tparams3, "IviiiIsssssssss"); + refreshTradeScreen(false); + tradeOpen = true; + exchangeStage = true; + + } + + /** + * Adds the trade item. + * + * @param player + * the player + * @param slot + * the slot + * @param amount + * the amount + */ + public void addTradeItem(Player player, int slot, int amount) { + if (!exchangeStage) + return; + int index = -1; + for (int i = 0; i < players.length; i++) + if (players[i].equals(player)) + index = i; + if (index == -1) + return; + Item item = player.getInventory().getItem(slot); + if (item == null) + return; + if (!ItemDef.forId(item.getId()).isTradeable() && player.getPlayerStatus() < 2) { + player.getEventWriter().sendMessage("That item is untradeable!"); + return; + } + int amountOfItem = player.getInventory().amountOfItem(item.getId()); + if (amountOfItem < amount) + amount = amountOfItem; + int amountCanHold = containers[index][0].amountCanHold(item.getId()); + if (amountCanHold < amount) + amount = amountCanHold; + if (player.getInventory().removeById(item.getId(), amount)) { + containers[index][0].add(item.getId(), amount); + containers[1 - index][1].setItems(containers[index][0].getItems()); + containers[1 - index][1].refresh(); + } + playerAccepted = null; + refreshTradeScreen(false); + } + + /** + * Removes the trade item. + * + * @param player + * the player + * @param slot + * the slot + * @param amount + * the amount + */ + public void removeTradeItem(Player player, int slot, int amount) { + if (!exchangeStage) + return; + int index = -1; + for (int i = 0; i < players.length; i++) + if (players[i].equals(player)) + index = i; + if (index == -1) + return; + Item item = containers[index][0].getItem(slot); + if (item == null) + return; + int amountOfItem = containers[index][0].amountOfItem(item.getId()); + if (amountOfItem < amount) + amount = amountOfItem; + int amountCanHold = player.getInventory().amountCanHold(item.getId()); + if (amountCanHold < amount) + amount = amountCanHold; + if (containers[index][0].removeById(item.getId(), amount)) { + containers[1 - index][1].setItems(containers[index][0].getItems()); + containers[1 - index][1].refresh(); + player.getInventory().add(item.getId(), amount); + } + playerAccepted = null; + refreshTradeScreen(false); + } + + /** + * Cancel. + * + * @param completed + * the completed + */ + public void cancel(boolean completed) { + for (int p = 0; p < getPlayers().length; p++) { + if (!completed) + players[p].addAllItems(containers[p][0].getItems()); + for (int i = 0; i < containers[p].length; i++) + containers[p][i].empty(); + players[p].getEventWriter().removeSideLockingInterface(); + + Action action = players[p].getCurrentAction(); + if (action != null && action instanceof TradeAction) + players[p].setCurrentAction(null); + if (players[p].getXValue() != null) + players[p].getXValue().cancel(false); + } + tradeOpen = false; + } + + /** + * Confirmation screen. + */ + public void confirmationScreen() { + exchangeStage = false; + for (int p = 0; p < players.length; p++) { + ItemContainer received = containers[1 - p][0]; + if (!players[p].getInventory().canFitAll(received.getItems())) { + players[p].getEventWriter().sendMessage("You do not have enough room to accept this trade!"); + players[1 - p].getEventWriter().sendMessage("The other player does not have enough room!"); + playerAccepted = null; + refreshTradeScreen(false); + return; + } + } + for (int p = 0; p < players.length; p++) { + players[p].getEventWriter().sendInterface(334); + players[p].getEventWriter().updateTabs(false); + } + playerAccepted = null; + refreshTradeScreen(true); + + } + + /** + * Complete trade. + */ + public void completeTrade() { + for (int p = 0; p < players.length; p++) { + ItemContainer received = containers[1 - p][0]; + players[p].addAllItems(received.getItems()); + players[p].getEventWriter().sendMessage("Trade completed."); + } + cancel(true); + } + + /** + * Gets the players. + * + * @return the players + */ + public Player[] getPlayers() { + return players; + } + + /** + * Gets the other. + * + * @param player + * the player + * @return the other + */ + public Player getOther(Player player) { + for (Player other : players) + if (!other.equals(player)) + return other; + return null; + } + + /** + * Accept. + * + * @param player + * the player + * @param confirmationScreen + * the confirmation screen + */ + public void accept(Player player, boolean confirmationScreen) { + if (playerAccepted == null) { + playerAccepted = player; + refreshTradeScreen(confirmationScreen); + } else if (!playerAccepted.equals(player)) { + if (!confirmationScreen) + confirmationScreen(); + else + completeTrade(); + } + } + + /** + * Refresh trade screen. + * + * @param confirmationScreen + * the confirmation screen + */ + public void refreshTradeScreen(boolean confirmationScreen) { + if (!confirmationScreen) { + for (int p = 0; p < players.length; p++) { + if (playerAccepted == null) + players[p].getEventWriter().sendString(" ", 335, 36); + else if (playerAccepted.equals(players[p])) + players[p].getEventWriter().sendString("Waiting for the other player to accept...", 335, 36); + else + players[p].getEventWriter().sendString("Other player has accepted the trade.", 335, 36); + players[p].getEventWriter().sendString("Trading With: " + players[1 - p].getUsername(), 335, 15); + } + } else { + String[][] itemStrings = new String[players.length][2]; + for (int p = 0; p < players.length; p++) { + itemStrings[p][0] = formItemString(containers[p][0], players[p]); + itemStrings[p][1] = formItemString(containers[p][1], players[p]); + } + for (int p = 0; p < players.length; p++) { + players[p].getEventWriter().sendString(itemStrings[p][0], 334, 37); + players[p].getEventWriter().sendString(itemStrings[p][1], 334, 41); + players[p].getEventWriter().sendString("Trading with:
" + "" + players[1 - p].getUsername(), 334, 46); + players[p].getEventWriter().sendInterfaceConfig(334, 37, false); + players[p].getEventWriter().sendInterfaceConfig(334, 41, false); + players[p].getEventWriter().sendInterfaceConfig(334, 45, true); + players[p].getEventWriter().sendInterfaceConfig(334, 46, false); + if (playerAccepted == null) + players[p].getEventWriter().sendString(" ", 334, 33); + else if (playerAccepted.equals(players[p])) + players[p].getEventWriter().sendString("Waiting for the other player to accept...", 334, 33); + else + players[p].getEventWriter().sendString("Other player has accepted the trade.", 334, 33); + } + } + + } + + /** + * Form item string. + * + * @param container + * the container + * @param player + * the player + * @return the string + */ + public String formItemString(ItemContainer container, Player player) { + String formation = ""; + if (container.countFilledSlots() != 0) { + for (int i = 0; i < container.countFilledSlots(); i++) { + Item item = container.getItem(i); + ItemDef def = ItemDef.forId(container.getItem(i).getId()); + formation += "" + def.getName(); + if (item.getAmount() > 1) + formation += " x " + item.getAmount() + ""; + formation += "
"; + } + } else + formation = "Absolutely nothing!"; + return formation; + } + + /** + * Gets the trade items. + * + * @param player + * the player + * @return the trade items + */ + public ItemContainer getTradeItems(Player player) { + int slot = -1; + for (int p = 0; p < players.length; p++) + if (players[p].equals(player)) + slot = p; + if (slot == -1) + return null; + return containers[slot][0]; + } +} diff --git a/src/osiris/game/model/magic/CombatSpell.java b/src/osiris/game/model/magic/CombatSpell.java new file mode 100644 index 0000000..81efb35 --- /dev/null +++ b/src/osiris/game/model/magic/CombatSpell.java @@ -0,0 +1,183 @@ +package osiris.game.model.magic; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.util.ArrayList; + +import osiris.ServerEngine; +import osiris.game.model.Character; +import osiris.game.model.Player; +import osiris.game.model.combat.CombatUtilities; +import osiris.game.model.combat.Hit; +import osiris.game.model.combat.HitType; +import osiris.game.model.combat.Projectile; +import osiris.game.model.effect.ExpiringEffect; +import osiris.util.Utilities; + +// TODO: Auto-generated Javadoc +/** + * The Class CombatSpell. + * + * @author Boomer + */ +public class CombatSpell extends Spell { + + /** The hit graphic. */ + private int maxDamage, hitDelay, projectileId, hitGraphic; + + /** The leech. */ + private double leech; + + /** The multi target. */ + private boolean multiTarget; + + /** The hit type. */ + private HitType hitType = HitType.MAGIC; + + /** + * Instantiates a new combat spell. + * + * @param level + * the level + * @param animation + * the animation + * @param graphic + * the graphic + * @param expEarned + * the exp earned + * @param runesRequired + * the runes required + * @param maxDamage + * the max damage + * @param hitDelay + * the hit delay + * @param projectileId + * the projectile id + * @param hitGraphic + * the hit graphic + */ + public CombatSpell(int level, int animation, int graphic, int expEarned, int[][] runesRequired, int maxDamage, int hitDelay, int projectileId, int hitGraphic) { + super(level, animation, graphic, expEarned, runesRequired); + this.maxDamage = maxDamage; + this.hitDelay = hitDelay; + this.projectileId = projectileId; + this.hitGraphic = hitGraphic; + this.leech = 0.0; + this.multiTarget = false; + } + + /** + * Enable multi target. + * + * @return the combat spell + */ + public CombatSpell enableMultiTarget() { + this.multiTarget = true; + return this; + } + + /** + * Sets the leech. + * + * @param leech + * the leech + * @return the combat spell + */ + public CombatSpell setLeech(double leech) { + this.leech = leech; + return this; + } + + /* + * (non-Javadoc) + * + * @see + * osiris.game.model.magic.Spell#onExecution(osiris.game.model.Character, + * java.lang.Object[]) + */ + @Override + public boolean onExecution(Character character, Object... params) { + Character victim = (Character) params[0]; + ExpiringEffect effect = null; + if (params.length > 1) + effect = (ExpiringEffect) params[params.length - 1]; + if (projectileId != -1) { + Projectile projectile = new Projectile(projectileId, character.getPosition(), victim.getPosition(), 50, 90, new int[] { 46, 31 }, victim, 0); + character.getCombatManager().getProjectiles().add(projectile); + } + Hit hit = null; + ArrayList nearCharacters = new ArrayList(); + if (multiTarget && !CombatUtilities.singleCombat(victim.getPosition())) { + if (victim instanceof Player) + nearCharacters.addAll(victim.getLocalPlayers()); + else if (character instanceof Player) + nearCharacters.addAll(character.getLocalPlayers()); + else + nearCharacters.addAll(victim.getLocalPlayers()); + if (victim instanceof Player) + nearCharacters.addAll(((Player) victim).getLocalNpcs()); + else if (character instanceof Player) + nearCharacters.addAll(((Player) character).getLocalNpcs()); + } + while (nearCharacters.contains(character)) + nearCharacters.remove(character); + if (!nearCharacters.contains(victim)) + nearCharacters.add(victim); + for (Character near : nearCharacters) { + if (!character.getCombatManager().canAttack(near)) + continue; + if (Utilities.getDistance(victim.getPosition(), near.getPosition()) > 1) + continue; + if (maxDamage > 0) + hit = new Hit(character, near, Utilities.random(maxDamage), hitDelay, hitType); + else if (maxDamage == -1) + hit = new Hit(character, near, -1, hitDelay, hitType); + if (hit != null) { + if (hitGraphic != -1) + hit.setGraphic(hitGraphic, character.getSpellBook() == SpellBook.ANCIENT ? 0 : 100); + if (leech != 0) + hit.setLeech(leech); + if (effect != null) + hit.setEffects(effect); + ServerEngine.getHitQueue().add(hit); + } + } + return true; + } + + /** + * Sets the max damage. + * + * @param damage + * the new max damage + */ + public void setMaxDamage(int damage) { + this.maxDamage = damage; + } + + /** + * Sets the hit type. + * + * @param hitType + * the new hit type + */ + public void setHitType(HitType hitType) { + this.hitType = hitType; + } +} diff --git a/src/osiris/game/model/magic/EffectSpell.java b/src/osiris/game/model/magic/EffectSpell.java new file mode 100644 index 0000000..418eb7d --- /dev/null +++ b/src/osiris/game/model/magic/EffectSpell.java @@ -0,0 +1,108 @@ +package osiris.game.model.magic; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.model.Character; +import osiris.game.model.Player; +import osiris.game.model.effect.CombatEffect; + +// TODO: Auto-generated Javadoc +/** + * The Class EffectSpell. + * + * @author Boomer + */ +public class EffectSpell extends CombatSpell { + + /** The cooldown. */ + private int cooldown; + + /** The clazz. */ + private Class clazz; + + /** The params. */ + private Object[] params; + + /** The requires effect. */ + private boolean requiresEffect; + + /** + * Instantiates a new effect spell. + * + * @param level + * the level + * @param animation + * the animation + * @param graphic + * the graphic + * @param expEarned + * the exp earned + * @param runesRequired + * the runes required + * @param maxDamage + * the max damage + * @param hitDelay + * the hit delay + * @param projectileId + * the projectile id + * @param hitGraphic + * the hit graphic + * @param requiresEffect + * the requires effect + * @param cooldown + * the cooldown + * @param clazz + * the clazz + * @param params + * the params + */ + public EffectSpell(int level, int animation, int graphic, int expEarned, int[][] runesRequired, int maxDamage, int hitDelay, int projectileId, int hitGraphic, boolean requiresEffect, int cooldown, Class clazz, Object... params) { + super(level, animation, graphic, expEarned, runesRequired, maxDamage, hitDelay, projectileId, hitGraphic); + this.cooldown = cooldown; + this.clazz = clazz; + this.params = params; + this.requiresEffect = requiresEffect; + } + + /* + * (non-Javadoc) + * + * @see + * osiris.game.model.magic.CombatSpell#onExecution(osiris.game.model.Character + * , java.lang.Object[]) + */ + @Override + public boolean onExecution(Character character, Object... parameters) { + Character victim = (Character) parameters[0]; + CombatEffect effect = CombatEffect.create(character, this.cooldown, this.clazz, this.params); + if (effect == null) { + return false; + } + boolean canAdd = victim.canAddEffect(effect); + if (!canAdd && !requiresEffect) + effect = null; + if (canAdd || !requiresEffect) { + return super.onExecution(character, victim, effect); + } else { + if (character instanceof Player && requiresEffect) + ((Player) character).getEventWriter().sendMessage("That " + victim.getClass().getSimpleName().toLowerCase() + "" + " is immune to that effect right now!"); + } + return false; + } +} diff --git a/src/osiris/game/model/magic/HomeTeleportSpell.java b/src/osiris/game/model/magic/HomeTeleportSpell.java new file mode 100644 index 0000000..d95e21e --- /dev/null +++ b/src/osiris/game/model/magic/HomeTeleportSpell.java @@ -0,0 +1,73 @@ +package osiris.game.model.magic; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.action.impl.HomeTeleportAction; +import osiris.game.model.Character; +import osiris.game.model.Player; +import osiris.game.model.Position; +import osiris.util.Utilities; + +// TODO: Auto-generated Javadoc +/** + * The Class HomeTeleportSpell. + * + * @author Boomer + */ +public class HomeTeleportSpell extends TeleportSpell { + + /** The Constant COOLDOWN_TICKS. */ + public static final int COOLDOWN_TICKS = 1000; + + /** + * Instantiates a new home teleport spell. + * + * @param to + * the to + * @param radiusX + * the radius x + * @param radiusY + * the radius y + */ + public HomeTeleportSpell(Position to, int radiusX, int radiusY) { + super(0, 0, null, false, to, radiusX, radiusY); + } + + /* + * (non-Javadoc) + * + * @see + * osiris.game.model.magic.TeleportSpell#onExecution(osiris.game.model.Character + * , java.lang.Object[]) + */ + @Override + public boolean onExecution(Character character, Object... params) { + if (!(character.getCurrentAction() instanceof HomeTeleportAction)) { + int elapsed = character.getHomeTeleTimer().elapsed(); + if (elapsed < COOLDOWN_TICKS) { + if (character instanceof Player) + ((Player) character).getEventWriter().sendMessage("You need to wait " + Utilities.ticksToMins(COOLDOWN_TICKS - elapsed) + " more minutes before casting that spell!"); + return false; + } + new HomeTeleportAction(character, calculatePosition()).run(); + return true; + } + return false; + } +} diff --git a/src/osiris/game/model/magic/MagicManager.java b/src/osiris/game/model/magic/MagicManager.java new file mode 100644 index 0000000..b6a2e92 --- /dev/null +++ b/src/osiris/game/model/magic/MagicManager.java @@ -0,0 +1,169 @@ +package osiris.game.model.magic; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.model.Position; +import osiris.game.model.Skills; +import osiris.game.model.effect.BindingSpellEffect; +import osiris.game.model.effect.PoisonEffect; +import osiris.game.model.effect.StatEffect; +import osiris.util.Settings; + +// TODO: Auto-generated Javadoc +/** + * The Class MagicManager. + * + * @author Boomer + */ +public class MagicManager { + + /** The lunar magic. */ + private Spell[] normalMagic, ancientMagick, lunarMagic; + + /** The manager. */ + private static MagicManager manager; + + /** The Constant SPELL_DELAY. */ + public static final int SPELL_DELAY = 6; + + /** The Constant NORMAL_AUTOCAST_SPELLS. */ + public static final int[] NORMAL_AUTOCAST_SPELLS = { 1, 4, 6, 8, 10, 14, 17, 20, 24, 27, 33, 38, 45, 48, 52, 55 }; + + /** The Constant ANCIENT_AUTOCAST_SPELLS. */ + public static final int[] ANCIENT_AUTOCAST_SPELLS = { 8, 12, 4, 0, 10, 14, 6, 2, 9, 13, 5, 1, 11, 15, 7, 3 }; + + /** + * Instantiates a new magic manager. + */ + public MagicManager() { + this.normalMagic = new Spell[] { + /* 0 */new HomeTeleportSpell(new Position(Settings.DEFAULT_X, Settings.DEFAULT_Y), 1, 1), new CombatSpell(1, 711, 90, 6, new int[][] { { 556, 1 }, { 558, 1 } }, 2, 3, 91, 92), new EffectSpell(3, 716, 102, 13, new int[][] { { 555, 3 }, { 557, 2 }, { 559, 1 } }, -1, 3, 103, 104, true, 60, StatEffect.class, Skills.SKILL_ATTACK, -.05), null,// crossbow + new CombatSpell(5, 711, 93, 8, new int[][] { { 555, 1 }, { 556, 1 }, { 558, 1 } }, 4, 3, 94, 95), + /* 5 */null,// enchant + new CombatSpell(9, 711, 96, 10, new int[][] { { 557, 2 }, { 556, 1 }, { 558, 1 } }, 6, 3, 97, 98), new EffectSpell(11, 716, 105, 21, new int[][] { { 555, 3 }, { 557, 2 }, { 559, 1 } }, -1, 3, 106, 107, true, 60, StatEffect.class, Skills.SKILL_STRENGTH, -.05), new CombatSpell(13, 711, 99, 12, new int[][] { { 554, 3 }, { 556, 2 }, { 558, 1 } }, 8, 3, 100, 101), null,// bones + /* 10 */new CombatSpell(17, 711, 117, 14, new int[][] { { 556, 2 }, { 562, 1 } }, 9, 3, 118, 119), new EffectSpell(19, 716, 108, 29, new int[][] { { 555, 2 }, { 557, 3 }, { 559, 1 } }, -1, 3, 109, 110, true, 60, StatEffect.class, Skills.SKILL_DEFENCE, -.05), new EffectSpell(20, 1164, 177, 30, new int[][] { { 557, 3 }, { 555, 3 }, { 561, 2 } }, -1, 3, 178, 181, true, 15, BindingSpellEffect.class, 5), null, new CombatSpell(23, 711, 120, 17, new int[][] { { 555, 2 }, { 556, 2 }, { 562, 1 } }, 10, 3, 121, 122), + /* 15 */new TeleportSpell(25, 0, new int[][] { { 554, 1 }, { 556, 3 }, { 563, 1 } }, false, new Position(3210, 3422, 0), 6, 2), // varrock + null,// enchant + new CombatSpell(29, 711, 123, 20, new int[][] { { 557, 3 }, { 556, 2 }, { 562, 1 } }, 11, 3, 124, 125), new TeleportSpell(31, 0, new int[][] { { 557, 1 }, { 556, 3 }, { 563, 1 } }, false, new Position(3222, 3222, 0), 1, 1), // lumby + null,// tele grab + /* 20 */new CombatSpell(35, 711, 126, 23, new int[][] { { 554, 4 }, { 556, 3 }, { 562, 1 } }, 12, 3, 127, 128), new TeleportSpell(37, 0, new int[][] { { 555, 1 }, { 556, 3 }, { 563, 1 } }, false, new Position(2963, 3379, 0), 4, 4), // fally + new CombatSpell(39, 711, 145, 25, new int[][] { { 557, 2 }, { 556, 2 }, { 562, 1 } }, 15, 3, 146, 147), null,// house + // tele + new CombatSpell(41, 711, 132, 26, new int[][] { { 556, 3 }, { 560, 1 } }, 13, 3, 133, 134), + /* 25 */null,// superheat + new TeleportSpell(45, 0, new int[][] { { 563, 1 }, { 556, 5 } }, false, new Position(2756, 3476, 0), 2, 3), // cammy + new CombatSpell(47, 711, 135, 29, new int[][] { { 555, 3 }, { 556, 3 }, { 560, 1 } }, 14, 3, 136, 137), null,// enchant + new StaffSpell(50, 711, 87, 30, new int[][] { { 554, 5 }, { 560, 1 } }, 25, 3, 88, 89, Settings.SPELL_STAFFS[Settings.STAFF_IBAN]), + /* 30 */new EffectSpell(50, 1164, 177, 60, new int[][] { { 557, 4 }, { 555, 4 }, { 561, 3 } }, 2, 3, 178, 180, true, 20, BindingSpellEffect.class, 10), new StaffSpell(50, 711, -1, 30, new int[][] { { 560, 1 }, { 558, 4 } }, 19, 3, 280, 281, Settings.SPELL_STAFFS[Settings.STAFF_SLAYER]), new TeleportSpell(51, 0, new int[][] { { 555, 2 }, { 563, 2 } }, false, new Position(2660, 3303, 0), 4, 2), // ardy + new CombatSpell(53, 711, 138, 32, new int[][] { { 557, 4 }, { 556, 3 }, { 560, 1 } }, 15, 3, 139, 140), null,// high + // enchant + /* 35 */null,// charge + null,// enchant + null,// watch tele + new CombatSpell(59, 711, 129, 35, new int[][] { { 554, 5 }, { 556, 4 }, { 560, 1 } }, 16, 3, 130, 131), null,// charge + /* 40 */null,// bones to peaches + new StaffSpell(60, 811, -1, 35, new int[][] { { 554, 2 }, { 565, 2 }, { 556, 4 } }, 20, 3, -1, 76, Settings.SPELL_STAFFS[Settings.STAFF_SARADOMIN]), new StaffSpell(60, 811, -1, 35, new int[][] { { 554, 1 }, { 565, 2 }, { 556, 4 } }, 20, 3, -1, 77, Settings.SPELL_STAFFS[Settings.STAFF_GUTHIX]), new StaffSpell(60, 811, -1, 35, new int[][] { { 554, 4 }, { 565, 2 }, { 556, 1 } }, 20, 3, -1, 78, Settings.SPELL_STAFFS[Settings.STAFF_ZAMORAK]), null,// troll + // tele + /* 45 */new CombatSpell(62, 711, 158, -36, new int[][] { { 556, 5 }, { 565, 1 } }, 17, 3, 159, 160), null,// charge + null,// ape tele + new CombatSpell(65, 711, 161, 38, new int[][] { { 555, 7 }, { 556, 5 }, { 565, 1 } }, 18, 3, 162, 163), null,// charge + /* 50 */new EffectSpell(66, 729, 167, 76, new int[][] { { 557, 5 }, { 555, 5 }, { 566, 1 } }, -1, 3, 168, 169, true, 60, StatEffect.class, Skills.SKILL_ATTACK, -.1), null,// enchant + new CombatSpell(70, 711, 164, 40, new int[][] { { 557, 7 }, { 556, 5 }, { 565, 1 } }, 19, 3, 165, 166), new EffectSpell(73, 729, 170, 83, new int[][] { { 557, 8 }, { 555, 8 }, { 566, 1 } }, -1, 3, 171, 172, true, 60, StatEffect.class, Skills.SKILL_STRENGTH, -.1), null,// lumby + // teleother + /* 55 */new CombatSpell(75, 711, 155, 43, new int[][] { { 554, 7 }, { 556, 5 }, { 565, 1 } }, 20, 3, 156, 157), new EffectSpell(79, 1164, 177, 89, new int[][] { { 557, 5 }, { 555, 5 }, { 561, 4 } }, 4, 3, 178, 179, true, 25, BindingSpellEffect.class, 15), new EffectSpell(80, 729, 173, 90, new int[][] { { 557, 12 }, { 555, 12 }, { 566, 1 } }, -1, 3, 174, 107, true, 60, StatEffect.class, Skills.SKILL_DEFENCE, -.1), null,// charge + null,// fally tele other + null,// enchant + /* 60 */null // cammy teleother + + }; + + // Ice, Blood, Smoke, Shadow + // 4, 12, 8, 16 3,11,7,15 1,9,5,13 + this.ancientMagick = new Spell[] { + /* 4 */new EffectSpell(58, 1978, -1, 34, new int[][] { { 562, 2 }, { 560, 2 }, { 555, 2 } }, 18, 3, 360, 361, false, 15, BindingSpellEffect.class, 5), + /* 12 */new EffectSpell(82, 1978, 366, 46, new int[][] { { 560, 2 }, { 565, 2 }, { 555, 3 } }, 26, 5, -1, 367, false, 25, BindingSpellEffect.class, 15).setSpellDelay(SPELL_DELAY - 1), + /* 8 */new EffectSpell(70, 1979, -1, 40, new int[][] { { 562, 4 }, { 560, 2 }, { 555, 4 } }, 22, 3, -1, 363, false, 20, BindingSpellEffect.class, 10).enableMultiTarget(), + /* 16 */new EffectSpell(94, 1979, -1, 52, new int[][] { { 560, 4 }, { 565, 2 }, { 555, 6 } }, 30, 3, -1, 369, false, 30, BindingSpellEffect.class, 20).enableMultiTarget(), + + /* 3 */new CombatSpell(56, 1978, -1, 33, new int[][] { { 562, 2 }, { 560, 2 }, { 565, 1 } }, 17, 3, 374, 373).setLeech(.25), + /* 11 */new CombatSpell(80, 1978, -1, 45, new int[][] { { 560, 2 }, { 565, 4 } }, 25, 5, -1, 376).setLeech(.25).setSpellDelay(SPELL_DELAY - 1), + /* 7 */new CombatSpell(68, 1979, -1, 39, new int[][] { { 562, 4 }, { 560, 2 }, { 565, 2 } }, 21, 3, -1, 375).setLeech(.25).enableMultiTarget(), + /* 15 */new CombatSpell(92, 1979, -1, 51, new int[][] { { 560, 4 }, { 565, 4 }, { 566, 1 } }, 29, 3, -1, 377).setLeech(.25).enableMultiTarget(), + + /* 1 */new EffectSpell(50, 1978, -1, 30, new int[][] { { 562, 2 }, { 560, 2 }, { 554, 1 }, { 556, 1 } }, 15, 3, 384, 385, false, 0, PoisonEffect.class, 2.0), + /* 9 */new EffectSpell(74, 1978, -1, 42, new int[][] { { 560, 2 }, { 565, 2 }, { 554, 2 }, { 556, 2 } }, 23, 5, -1, 389, false, 0, PoisonEffect.class, 4.0).setSpellDelay(SPELL_DELAY - 1), + /* 5 */new EffectSpell(62, 1979, -1, 36, new int[][] { { 562, 4 }, { 560, 2 }, { 554, 2 }, { 556, 2 } }, 19, 3, -1, 388, false, 0, PoisonEffect.class, 2.0).enableMultiTarget(), + /* 13 */new EffectSpell(84, 1979, -1, 48, new int[][] { { 560, 4 }, { 565, 2 }, { 554, 4 }, { 556, 4 } }, 27, 3, -1, 390, false, 0, PoisonEffect.class, 4.0).enableMultiTarget(), + + /* 2 */new EffectSpell(52, 1978, -1, 31, new int[][] { { 562, 2 }, { 560, 2 }, { 556, 1 }, { 566, 1 } }, 16, 3, 380, 379, false, 60, StatEffect.class, Skills.SKILL_ATTACK, -.05), + /* 10 */new EffectSpell(76, 1978, -1, 43, new int[][] { { 560, 2 }, { 565, 2 }, { 556, 2 }, { 566, 2 } }, 24, 5, -1, 382, false, 60, StatEffect.class, Skills.SKILL_ATTACK, -.15).setSpellDelay(SPELL_DELAY - 1), + /* 6 */new EffectSpell(64, 1979, -1, 37, new int[][] { { 562, 4 }, { 560, 2 }, { 556, 2 }, { 566, 2 } }, 20, 3, -1, 381, false, 60, StatEffect.class, Skills.SKILL_ATTACK, -.10).enableMultiTarget(), + /* 14 */new EffectSpell(88, 1979, -1, 49, new int[][] { { 560, 4 }, { 565, 2 }, { 556, 4 }, { 566, 3 } }, 28, 3, -1, 383, false, 60, StatEffect.class, Skills.SKILL_ATTACK, -.20).enableMultiTarget(), null, null, null, null, null, null, null, null, new HomeTeleportSpell(new Position(Settings.DEFAULT_X, Settings.DEFAULT_Y), 1, 1) }; + this.lunarMagic = new Spell[] { + + }; + } + + /** + * Gets the spell. + * + * @param index + * the index + * @param book + * the book + * @return the spell + */ + public static Spell getSpell(int index, SpellBook book) { + if (book == SpellBook.NORMAL) { + if (index >= manager.normalMagic.length) + return null; + return manager.normalMagic[index]; + } else if (book == SpellBook.ANCIENT) { + if (index >= manager.ancientMagick.length) + return null; + return manager.ancientMagick[index]; + } else if (book == SpellBook.LUNAR) { + if (index >= manager.lunarMagic.length) + return null; + return manager.lunarMagic[index]; + } + return null; + } + + /** + * Load. + */ + public static void load() { + manager = new MagicManager(); + } + + /** + * Gets the spell book. + * + * @param spellBook + * the spell book + * @return the spell book + */ + public static Spell[] getSpellBook(SpellBook spellBook) { + if (spellBook == SpellBook.NORMAL) + return manager.normalMagic; + return null; + } + +} diff --git a/src/osiris/game/model/magic/Spell.java b/src/osiris/game/model/magic/Spell.java new file mode 100644 index 0000000..6d5a5c3 --- /dev/null +++ b/src/osiris/game/model/magic/Spell.java @@ -0,0 +1,218 @@ +package osiris.game.model.magic; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import osiris.game.model.Character; +import osiris.game.model.Player; +import osiris.game.model.Skills; +import osiris.game.model.def.ItemDef; +import osiris.game.model.item.Item; +import osiris.game.update.block.AnimationBlock; +import osiris.game.update.block.GraphicsBlock; +import osiris.util.Settings; + +// TODO: Auto-generated Javadoc +/** + * The Class Spell. + * + * @author Boomer + */ +public abstract class Spell { + + /** The runes required. */ + private int[][] runesRequired; + + /** The delay. */ + private int levelRequired, animation, graphic, delay; + + /** The exp earned. */ + private double expEarned; + + /** + * Instantiates a new spell. + * + * @param levelRequired + * the level required + * @param animation + * the animation + * @param graphic + * the graphic + * @param expEarned + * the exp earned + * @param runesRequired + * the runes required + */ + public Spell(int levelRequired, int animation, int graphic, double expEarned, int[][] runesRequired) { + this.levelRequired = levelRequired; + this.animation = animation; + this.graphic = graphic; + this.expEarned = expEarned; + this.runesRequired = runesRequired; + this.delay = MagicManager.SPELL_DELAY; + } + + /** + * Gets the animation. + * + * @return the animation + */ + public int getAnimation() { + return animation; + } + + /** + * Gets the graphic. + * + * @return the graphic + */ + public int getGraphic() { + return graphic; + } + + /** + * Gets the exp earned. + * + * @return the exp earned + */ + public double getExpEarned() { + return expEarned; + } + + /** + * Gets the runes required. + * + * @return the runes required + */ + public int[][] getRunesRequired() { + return runesRequired; + } + + /** + * Gets the delay. + * + * @return the delay + */ + public int getDelay() { + return delay; + } + + /** + * Sets the spell delay. + * + * @param delay + * the delay + * @return the spell + */ + public Spell setSpellDelay(int delay) { + this.delay = delay; + return this; + } + + /** + * Execute. + * + * @param character + * the character + * @param defence + * the defence + * @param params + * the params + * @return true, if successful + */ + public boolean execute(Character character, boolean defence, Object... params) { + if (character instanceof Player) { + if (((Player) character).getSkills().currentLevel(Skills.SKILL_MAGIC) < levelRequired) { + ((Player) character).getEventWriter().sendMessage("You need a higher level to use this spell!"); + return false; + } + if (runesRequired != null) { + for (int[] runes : runesRequired) { + if (usingStaff(((Player) character).getEquipment().getItem(Settings.SLOT_WEAPON), runes[0])) + continue; + if (((Player) character).getInventory().amountOfItem(runes[0]) < runes[1]) { + ((Player) character).getEventWriter().sendMessage("You do not have the runes necessary!"); + return false; + } + } + } + } + boolean executed = onExecution(character, params); + if (executed) { + if (character instanceof Player) { + if (runesRequired != null) { + for (int[] runes : runesRequired) + if (!usingStaff(((Player) character).getEquipment().getItem(Settings.SLOT_WEAPON), runes[0])) + ((Player) character).getInventory().removeById(runes[0], runes[1]); + } + ((Player) character).getSkills().addExp(Skills.SKILL_MAGIC, expEarned); + } + if (graphic != -1) + character.addPriorityUpdateBlock(new GraphicsBlock(character, graphic, character.getSpellBook() == SpellBook.ANCIENT ? 0 : 100)); + if (animation != -1) + + character.addPriorityUpdateBlock(new AnimationBlock(character, animation, 0)); + return true; + } + if (character instanceof Player) { + Spell curAutoSpell = character.getCombatManager().getAutoSpell(); + if (curAutoSpell != null && curAutoSpell == this) + character.getCombatManager().setAutoSpell(-1); + } + return false; + } + + /** + * Using staff. + * + * @param item + * the item + * @param runeId + * the rune id + * @return true, if successful + */ + public boolean usingStaff(Item item, int runeId) { + if (item == null) + return false; + ItemDef def = ItemDef.forId(item.getId()); + String name = def.getName().toLowerCase(); + if (!name.contains("staff")) + return false; + switch (runeId) { + case 554: + return name.contains("fire") || name.contains("lava"); + case 555: + return name.contains("water") || name.contains("mud"); + case 556: + return name.contains("air"); + case 557: + return name.contains("earth") || name.contains("lava"); + } + return false; + } + + /** + * On execution. + * + * @param character + * the character + * @param params + * the params + * @return true, if successful + */ + public abstract boolean onExecution(Character character, Object... params); +} diff --git a/src/osiris/game/model/magic/SpellBook.java b/src/osiris/game/model/magic/SpellBook.java new file mode 100644 index 0000000..81f5fd1 --- /dev/null +++ b/src/osiris/game/model/magic/SpellBook.java @@ -0,0 +1,64 @@ +package osiris.game.model.magic; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * The Enum SpellBook. + * + * @author Boomer + */ +public enum SpellBook { + + NORMAL(192, -1), ANCIENT(193, -1), LUNAR(192, -1); + + /** The auto interface id. */ + int interfaceId, autoInterfaceId; + + /** + * Instantiates a new spell book. + * + * @param interfaceId + * the interface id + * @param autoInterfaceId + * the auto interface id + */ + SpellBook(int interfaceId, int autoInterfaceId) { + this.interfaceId = interfaceId; + this.autoInterfaceId = autoInterfaceId; + } + + /** + * Gets the interface id. + * + * @return the interface id + */ + public int getInterfaceId() { + return interfaceId; + } + + /** + * Gets the auto interface id. + * + * @return the auto interface id + */ + public int getAutoInterfaceId() { + return autoInterfaceId; + } + +} diff --git a/src/osiris/game/model/magic/StaffSpell.java b/src/osiris/game/model/magic/StaffSpell.java new file mode 100644 index 0000000..536353b --- /dev/null +++ b/src/osiris/game/model/magic/StaffSpell.java @@ -0,0 +1,86 @@ +package osiris.game.model.magic; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.model.Character; +import osiris.game.model.Player; +import osiris.game.model.def.ItemDef; +import osiris.game.model.item.Item; +import osiris.util.Settings; + +// TODO: Auto-generated Javadoc +/** + * The Class StaffSpell. + * + * @author Boomer + */ +public class StaffSpell extends CombatSpell { + + /** The staff required. */ + private int staffRequired; + + /** + * Instantiates a new staff spell. + * + * @param level + * the level + * @param animation + * the animation + * @param graphic + * the graphic + * @param expEarned + * the exp earned + * @param runesRequired + * the runes required + * @param maxDamage + * the max damage + * @param hitDelay + * the hit delay + * @param projectileId + * the projectile id + * @param hitGraphic + * the hit graphic + * @param staffRequired + * the staff required + */ + public StaffSpell(int level, int animation, int graphic, int expEarned, int[][] runesRequired, int maxDamage, int hitDelay, int projectileId, int hitGraphic, int staffRequired) { + super(level, animation, graphic, expEarned, runesRequired, maxDamage, hitDelay, projectileId, hitGraphic); + this.staffRequired = staffRequired; + } + + /* + * (non-Javadoc) + * + * @see + * osiris.game.model.magic.CombatSpell#onExecution(osiris.game.model.Character + * , java.lang.Object[]) + */ + @Override + public boolean onExecution(Character character, Object... parameters) { + if (character instanceof Player) { + Item weapon = ((Player) character).getEquipment().getItem(Settings.SLOT_WEAPON); + if (weapon == null || weapon.getId() != staffRequired) { + ((Player) character).getEventWriter().sendMessage("You need to wield a " + ItemDef.forId(staffRequired).getName() + " in order to cast this spell!"); + return false; + } + } + return super.onExecution(character, parameters); + } + +} diff --git a/src/osiris/game/model/magic/TeleportSpell.java b/src/osiris/game/model/magic/TeleportSpell.java new file mode 100644 index 0000000..441a8a3 --- /dev/null +++ b/src/osiris/game/model/magic/TeleportSpell.java @@ -0,0 +1,136 @@ +package osiris.game.model.magic; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.action.impl.TeleportAction; +import osiris.game.model.Character; +import osiris.game.model.Position; +import osiris.util.Utilities; + +// TODO: Auto-generated Javadoc +/** + * The Class TeleportSpell. + * + * @author Boomer + */ +public class TeleportSpell extends Spell { + + /** The to. */ + private Position to; + + /** The ancients. */ + private boolean ancients; + + /** The radius y. */ + private int radiusX, radiusY; + + /** + * Instantiates a new teleport spell. + * + * @param levelRequired + * the level required + * @param expEarned + * the exp earned + * @param runesRequired + * the runes required + * @param ancients + * the ancients + * @param to + * the to + * @param radiusX + * the radius x + * @param radiusY + * the radius y + */ + public TeleportSpell(int levelRequired, double expEarned, int[][] runesRequired, boolean ancients, Position to, int radiusX, int radiusY) { + super(levelRequired, -1, -1, expEarned, runesRequired); + this.ancients = ancients; + this.to = to; + this.radiusX = radiusX; + this.radiusY = radiusY; + } + + /** + * Instantiates a new teleport spell. + * + * @param to + * the to + * @param radiusX + * the radius x + * @param radiusY + * the radius y + * @param ancients + * the ancients + */ + public TeleportSpell(Position to, int radiusX, int radiusY, boolean ancients) { + super(0, -1, -1, 0, null); + this.ancients = ancients; + this.to = to; + this.radiusX = radiusX; + this.radiusY = radiusY; + } + + /** + * Instantiates a new teleport spell. + * + * @param to + * the to + * @param ancients + * the ancients + */ + public TeleportSpell(Position to, boolean ancients) { + this(to, 0, 0, ancients); + } + + /** + * Instantiates a new teleport spell. + * + * @param to + * the to + */ + public TeleportSpell(Position to) { + this(to, false); + } + + /* + * (non-Javadoc) + * + * @see + * osiris.game.model.magic.Spell#onExecution(osiris.game.model.Character, + * java.lang.Object[]) + */ + @Override + public boolean onExecution(Character character, Object... params) { + if (!(character.getCurrentAction() instanceof TeleportAction)) { + // TODO: make it check if player can teleport + new TeleportAction(character, calculatePosition(), (ancients ? TeleportAction.TeleportType.ANCIENT : TeleportAction.TeleportType.NORMAL)).run(); + return true; + } + return false; + } + + /** + * Calculate position. + * + * @return the position + */ + public Position calculatePosition() { + return new Position(to.getX() + Utilities.random(radiusX), to.getY() + Utilities.random(radiusY)); + } +} diff --git a/src/osiris/game/model/skills/Cooking.java b/src/osiris/game/model/skills/Cooking.java new file mode 100644 index 0000000..0177a5a --- /dev/null +++ b/src/osiris/game/model/skills/Cooking.java @@ -0,0 +1,167 @@ +package osiris.game.model.skills; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.action.DistancedAction; +import osiris.game.action.TickedAction; +import osiris.game.action.impl.SkillAction; +import osiris.game.model.Player; +import osiris.game.model.Position; +import osiris.game.model.Skills; +import osiris.game.model.def.ItemDef; +import osiris.game.model.item.Item; +import osiris.game.update.block.FaceToPositionBlock; +import osiris.util.Utilities; + +// TODO: Auto-generated Javadoc +/** + * The Class Cooking. + * + * @author Boomer + */ +public class Cooking { + + /** + * To recipe name. + * + * @param itemName + * the item name + * @return the string + */ + public static String toRecipeName(String itemName) { + return itemName.toLowerCase().replaceAll("raw ", "").replaceAll(" ", "_").toUpperCase(); + } + + /** + * Cook item. + * + * @param player + * the player + * @param itemSlot + * the item slot + * @param recipe + * the recipe + * @param fire + * the fire + */ + public static void cookItem(final Player player, final Position objectPosition, int itemSlot, final Recipe recipe, final boolean fire) { + final Item food = player.getInventory().getItem(itemSlot); + final String foodName = ItemDef.forId(food.getId()).getName(); + final int amount = player.getInventory().amountOfItem(food.getId()); + final boolean burned = burnedFood(player, recipe); + final Skill cookingSkill = new Skill() { + + /* + * (non-Javadoc) + * + * @see osiris.game.model.skills.Skill#canIterate() + */ + @Override + public boolean canIterate() { + return true; + } + + /* + * (non-Javadoc) + * + * @see osiris.game.model.skills.Skill#calculateCooldown() + */ + @Override + public int calculateCooldown() { + return 4; + } + }; + + new DistancedAction(player, objectPosition, 1) { + + @Override + public void execute() { + player.addUpdateBlock(new FaceToPositionBlock(player, objectPosition)); + TickedAction action = new SkillAction(player, cookingSkill, Skills.SKILL_COOKING, recipe.requiredLevel, recipe.expGained, fire ? 897 : 896, new Item[0], new Item[] { Item.create(food.getId()) }, new Item[] { Item.create(burned ? recipe.burnId : recipe.cookedId) }, burned ? "You burn the " + foodName + "." : "You cook the " + foodName + ".", amount) { + + /* + * (non-Javadoc) + * + * @see osiris.game.action.impl.SkillAction#execute() + */ + @Override + public void execute() { + boolean burned = burnedFood(player, recipe); + this.setItemsGained(new Item[] { Item.create(burned ? recipe.burnId : recipe.cookedId) }); + this.setExperienceGained(burned ? 0 : recipe.expGained); + this.setSuccessMessage(burned ? "You burn the " + foodName + "." : "You cook the " + foodName + "."); + super.execute(); + } + }; + action.run(); + action.execute(); + } + }.run(); + } + + /** + * Burned food. + * + * @param player + * the player + * @param recipe + * the recipe + * @return true, if successful + */ + public static boolean burnedFood(Player player, Recipe recipe) { + int cookingLevel = player.getSkills().currentLevel(Skills.SKILL_COOKING); + return cookingLevel < recipe.masteredLevel && Utilities.random(cookingLevel) < Utilities.random(recipe.requiredLevel); + } + + /** + * The Enum Recipe. + */ + public enum Recipe { + + SHRIMPS(315, 323, 1, 34, 30), ANCHOVIES(315, 319, 1, 34, 30), HERRING(315, 347, 5, 37, 50), PIKE(315, 351, 20, 52, 80); + + /** The mastered level. */ + int cookedId, burnId, requiredLevel, masteredLevel; + + /** The exp gained. */ + double expGained; + + /** + * Instantiates a new recipe. + * + * @param cookedId + * the cooked id + * @param burnId + * the burn id + * @param requiredLevel + * the required level + * @param masteredLevel + * the mastered level + * @param expGained + * the exp gained + */ + Recipe(int cookedId, int burnId, int requiredLevel, int masteredLevel, double expGained) { + this.cookedId = cookedId; + this.burnId = burnId; + this.requiredLevel = requiredLevel; + this.masteredLevel = masteredLevel; + this.expGained = expGained; + } + } +} diff --git a/src/osiris/game/model/skills/Firemaking.java b/src/osiris/game/model/skills/Firemaking.java new file mode 100644 index 0000000..b98b4c7 --- /dev/null +++ b/src/osiris/game/model/skills/Firemaking.java @@ -0,0 +1,147 @@ +package osiris.game.model.skills; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.util.Random; + +import osiris.game.action.impl.SkillAction; +import osiris.game.action.object.ObjectSetter; +import osiris.game.model.Player; +import osiris.game.model.Position; +import osiris.game.model.Skills; +import osiris.game.model.WorldObject; +import osiris.game.model.WorldObjects; +import osiris.game.model.ground.GroundItem; +import osiris.game.model.ground.GroundManager; +import osiris.game.model.item.Item; +import osiris.game.update.block.AnimationBlock; +import osiris.util.Utilities; + +// TODO: Auto-generated Javadoc +/** + * The Class Firemaking. + * + * @author Boomer + */ +public class Firemaking { + + /** + * Gets the log name. + * + * @param itemName + * the item name + * @return the log name + */ + public static String getLogName(String itemName) { + return itemName.replaceAll(" ", "").replaceAll("logs", "").toUpperCase(); + } + + /** + * Light log. + * + * @param player + * the player + * @param itemSlot + * the item slot + * @param log + * the log + */ + public static void lightLog(final Player player, int itemSlot, final Log log) { + for (WorldObject object : WorldObjects.getObjects()) { + if (object != null && object.getPosition().equals(player.getPosition())) { + player.getEventWriter().sendMessage("You can not light a fire here!"); + return; + } + } + + Item logItem = player.getInventory().getItem(itemSlot); + Skill firemakingSkill = new Skill() { + + /* + * (non-Javadoc) + * + * @see osiris.game.model.skills.Skill#canIterate() + */ + @Override + public boolean canIterate() { + int randomSkill = Utilities.random(player.getSkills().currentLevel(Skills.SKILL_FIREMAKING)); + double randomRequired = new Random().nextDouble() * log.levelNeeded * 1.5; + return randomSkill >= randomRequired; + } + + /* + * (non-Javadoc) + * + * @see osiris.game.model.skills.Skill#calculateCooldown() + */ + @Override + public int calculateCooldown() { + return 2; + } + + /* + * (non-Javadoc) + * + * @see osiris.game.model.skills.Skill#onCompletion() + */ + @Override + public void onCompletion() { + player.addPriorityUpdateBlock(new AnimationBlock(player, -1, 0)); + WorldObject fire = new WorldObject(2732, 0, 10, player.getPosition()); + final Position firePosition = player.getPosition().clone(); + new ObjectSetter(null, fire, 45).setOnReset(new Runnable() { + @Override + public void run() { + GroundManager.getManager().dropItem(new GroundItem(Item.create(592), firePosition)); + } + }).execute(); + } + }; + + player.addUpdateBlock(new AnimationBlock(player, 10011, 0)); + new SkillAction(player, firemakingSkill, Skills.SKILL_FIREMAKING, log.levelNeeded, log.expGained, 10011, new Item[] { Item.create(590) }, new Item[] { logItem }, new Item[0], "You light a fire.", 1).setMaxAttempts(5).run(); + } + + /** + * The Enum Log. + */ + public enum Log { + + LOGS(1, 40), OAK(15, 60), WILLOW(30, 90), MAPLE(45, 135), YEW(60, 202.5), MAGIC(75, 303.8); + + /** The level needed. */ + int levelNeeded; + + /** The exp gained. */ + double expGained; + + /** + * Instantiates a new log. + * + * @param levelNeeded + * the level needed + * @param expGained + * the exp gained + */ + Log(int levelNeeded, double expGained) { + this.levelNeeded = levelNeeded; + this.expGained = expGained; + } + } +} diff --git a/src/osiris/game/model/skills/Fishing.java b/src/osiris/game/model/skills/Fishing.java new file mode 100644 index 0000000..c5b909c --- /dev/null +++ b/src/osiris/game/model/skills/Fishing.java @@ -0,0 +1,239 @@ +package osiris.game.model.skills; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.util.Random; + +import osiris.game.action.DistancedAction; +import osiris.game.action.impl.SkillAction; +import osiris.game.model.Player; +import osiris.game.model.Position; +import osiris.game.model.Skills; +import osiris.game.model.def.ItemDef; +import osiris.game.model.item.Item; +import osiris.game.update.block.FaceToPositionBlock; +import osiris.util.Utilities; + +// TODO: Auto-generated Javadoc +/** + * The Class Fishing. + * + * @author Boomer + */ +public class Fishing { + + /** + * Gets the fishing spot. + * + * @param objectId + * the object id + * @param option + * the option + * @return the fishing spot + */ + public static FishingSpot getFishingSpot(int objectId, int option) { + for (FishingSpot spot : FishingSpot.values()) + if (spot.option == option) + for (int id : spot.objectIds) + if (id == objectId) + return spot; + return null; + } + + /** + * Start fishing. + * + * @param player + * the player + * @param spot + * the spot + */ + public static void startFishing(final Player player, final Position fishingPosition, final FishingSpot spot) { + final Fish fish = getFishCaught(player, spot); + if (fish == null) + return; + final Skill fishingSkill = new Skill() { + + /* + * (non-Javadoc) + * + * @see osiris.game.model.skills.Skill#canIterate() + */ + @Override + public boolean canIterate() { + int randomSkill = Utilities.random(player.getSkills().currentLevel(Skills.SKILL_FISHING)); + double randomRequired = new Random().nextDouble() * fish.levelRequired * 3; + return randomSkill >= randomRequired; + } + + /* + * (non-Javadoc) + * + * @see osiris.game.model.skills.Skill#calculateCooldown() + */ + @Override + public int calculateCooldown() { + return 2; + } + }; + new DistancedAction(player, fishingPosition, 1) { + @Override + public void execute() { + player.addUpdateBlock(new FaceToPositionBlock(player, fishingPosition)); + new SkillAction(player, fishingSkill, Skills.SKILL_FISHING, fish.levelRequired, fish.expGained, spot.emote, new Item[] { Item.create(spot.itemRequired) }, spot.baitRequired != -1 ? new Item[] { Item.create(spot.baitRequired) } : new Item[0], new Item[] { Item.create(fish.itemId) }, "You catch " + ItemDef.forId(fish.itemId).getName() + ".", -1) { + + /* + * (non-Javadoc) + * + * @see osiris.game.action.impl.SkillAction#execute() + */ + @Override + public void execute() { + final Fish fish = getFishCaught(getPlayer(), spot); + if (fish == null) { + cancel(); + return; + } + this.setItemsGained(new Item[] { Item.create(fish.itemId) }); + this.setLevelRequired(fish.levelRequired); + this.setExperienceGained(fish.expGained); + this.setSuccessMessage("You catch " + ItemDef.forId(fish.itemId).getName() + "."); + super.execute(); + } + + }.run(); + } + }.run(); + } + + /** + * Gets the fish caught. + * + * @param player + * the player + * @param spot + * the spot + * @return the fish caught + */ + public static Fish getFishCaught(Player player, FishingSpot spot) { + Fish catching = null; + Random rand = new Random(); + int fishingLevel = player.getSkills().currentLevel(Skills.SKILL_FISHING); + for (Fish fish : spot.fishes) { + boolean canCatch = fishingLevel >= fish.levelRequired && rand.nextInt(fishingLevel + 1) >= rand.nextInt(fish.levelRequired + 1); + if (catching != null) { + if (rand.nextBoolean() && canCatch && catching.levelRequired < fish.levelRequired) + catching = fish; + } else + catching = fish; + } + return catching; + } + + /** + * The Enum FishingSpot. + */ + public enum FishingSpot { + + SHRIMPS_SPOT(new int[] { 316 }, 0, 620, new Fish[] { Fish.SHRIMPS, Fish.ANCHOVY }, 303), PIKE_SPOT(new int[] { 316 }, 1, 622, new Fish[] { Fish.HERRING, Fish.PIKE }, 307, 313); + + /** The bait required. */ + int option, emote, itemRequired, baitRequired; + + /** The object ids. */ + int[] objectIds; + + /** The fishes. */ + Fish[] fishes; + + /** + * Instantiates a new fishing spot. + * + * @param objectIds + * the object ids + * @param option + * the option + * @param emote + * the emote + * @param fishes + * the fishes + * @param itemRequired + * the item required + */ + FishingSpot(int[] objectIds, int option, int emote, Fish[] fishes, int itemRequired) { + this(objectIds, option, emote, fishes, itemRequired, -1); + } + + /** + * Instantiates a new fishing spot. + * + * @param objectIds + * the object ids + * @param option + * the option + * @param emote + * the emote + * @param fishes + * the fishes + * @param itemRequired + * the item required + * @param baitRequired + * the bait required + */ + FishingSpot(int[] objectIds, int option, int emote, Fish[] fishes, int itemRequired, int baitRequired) { + this.objectIds = objectIds; + this.option = option; + this.emote = emote; + this.fishes = fishes; + this.itemRequired = itemRequired; + this.baitRequired = baitRequired; + } + } + + /** + * The Enum Fish. + */ + public enum Fish { + + SHRIMPS(317, 1, 10), ANCHOVY(321, 15, 40), HERRING(345, 10, 30), PIKE(349, 25, 60); + + /** The level required. */ + int itemId, levelRequired; + + /** The exp gained. */ + double expGained; + + /** + * Instantiates a new fish. + * + * @param itemId + * the item id + * @param levelRequired + * the level required + * @param expGained + * the exp gained + */ + Fish(int itemId, int levelRequired, double expGained) { + this.itemId = itemId; + this.levelRequired = levelRequired; + this.expGained = expGained; + } + } + +} diff --git a/src/osiris/game/model/skills/Fletching.java b/src/osiris/game/model/skills/Fletching.java new file mode 100644 index 0000000..ae86ef0 --- /dev/null +++ b/src/osiris/game/model/skills/Fletching.java @@ -0,0 +1,373 @@ +package osiris.game.model.skills; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.action.impl.DisplaySelectOptionAction; +import osiris.game.action.impl.SkillAction; +import osiris.game.model.Player; +import osiris.game.model.Skills; +import osiris.game.model.def.ItemDef; +import osiris.game.model.item.Item; +import osiris.util.Utilities; + +// TODO: Auto-generated Javadoc + +/** + * The Class Fletching. + * + * TODO: Needs correct animations. + * + * @author Blake + * @author samuraiblood2 + * + */ +public class Fletching { + + /** The fletch item. */ + private static FletchItem fletchItem; + + /** + * Feather arrows. + * + * @param player + * the player + * @param item + * the item + * @param amount + * the amount + */ + public static void featherArrows(Player player, Item item, int amount) { + boolean feather = item.getId() == 314; + Item[] req = { feather ? Item.create(314) : Item.create(52), item }; + Item[] remove = { Item.create(314, 15), Item.create(52, 15) }; + Item[] give = { Item.create(53, 15) }; + String message = "You fletch a Headless arrow!"; + new SkillAction(player, new Skill() { + + /* + * (non-Javadoc) + * + * @see osiris.game.model.skills.Skill#calculateCooldown() + */ + @Override + public int calculateCooldown() { + return 1; + } + + /* + * (non-Javadoc) + * + * @see osiris.game.model.skills.Skill#canIterate() + */ + @Override + public boolean canIterate() { + return true; + } + + }, Skills.SKILL_FLETCHING, 1, 1, 1248, req, remove, give, message, 1).run(); + } + + /** + * Fletch arrows. + * + * @param player + * the player + * @param item + * the item + */ + public static void fletchArrows(Player player, Item item) { + ItemDef def = ItemDef.forId(item.getId()); + String name = def.getName().toUpperCase().replaceAll(" ", "_"); + fletchItem = FletchItem.valueOf(name); + if (fletchItem == null) { + return; + } + + Item[] req = { Item.create(53), item }; + Item[] remove = { Item.create(53, 15), Item.create(item.getId(), 15) }; + Item[] give = { Item.create(fletchItem.getCompleted(), 15) }; + String message = "You fletch a " + Utilities.toProperCase(ItemDef.forId(fletchItem.getCompleted()).getName()) + "!"; + new SkillAction(player, new Skill() { + + /* + * (non-Javadoc) + * + * @see osiris.game.model.skills.Skill#calculateCooldown() + */ + @Override + public int calculateCooldown() { + return 1; + } + + /* + * (non-Javadoc) + * + * @see osiris.game.model.skills.Skill#canIterate() + */ + @Override + public boolean canIterate() { + return true; + } + + }, Skills.SKILL_FLETCHING, fletchItem.getReq(), fletchItem.getExp(), 1248, req, remove, give, message, 1).run(); + } + + /** + * Fletch bow. + * + * @param player + * the player + * @param option + * the option + * @param amount + * the amount + */ + public static void fletchBow(Player player, int option, int amount) { + if (fletchItem == null) { + return; + } + + FletchItem bow = fletchItem.getItems()[option]; + Item give = Item.create(bow.getId()); + Item[] req = { Item.create(946), Item.create(fletchItem.getId()) }; + Item[] remove = { Item.create(fletchItem.getId()) }; + Item[] given = { bow.getId() == 52 ? Item.create(52, 15) : give }; + String message = "You fletch a " + Utilities.toProperCase(ItemDef.forId(give.getId()).getName()) + "!"; + new SkillAction(player, new Skill() { + + /* + * (non-Javadoc) + * + * @see osiris.game.model.skills.Skill#calculateCooldown() + */ + @Override + public int calculateCooldown() { + return 2; + } + + /* + * (non-Javadoc) + * + * @see osiris.game.model.skills.Skill#canIterate() + */ + @Override + public boolean canIterate() { + return true; + } + + }, Skills.SKILL_FLETCHING, bow.getReq(), bow.getExp(), 1248, req, remove, given, message, amount).run(); + } + + /** + * String bow. + * + * @param player + * the player + * @param item + * the item + */ + public static void stringBow(Player player, Item item) { + ItemDef def = ItemDef.forId(item.getId()); + String name = def.getName().toUpperCase().replaceAll(" ", "_"); + fletchItem = FletchItem.valueOf(name); + if (fletchItem == null) { + return; + } + + Item bowstring = Item.create(1777); + Item[] req = { bowstring, item }; + Item[] remove = { bowstring, item }; + Item[] give = { Item.create(fletchItem.getCompleted()) }; + String message = "You fletch a " + Utilities.toProperCase(ItemDef.forId(fletchItem.getCompleted()).getName()) + "!"; + new SkillAction(player, new Skill() { + + /* + * (non-Javadoc) + * + * @see osiris.game.model.skills.Skill#calculateCooldown() + */ + @Override + public int calculateCooldown() { + return 1; + } + + /* + * (non-Javadoc) + * + * @see osiris.game.model.skills.Skill#canIterate() + */ + @Override + public boolean canIterate() { + return true; + } + + }, Skills.SKILL_FLETCHING, fletchItem.getReq(), fletchItem.getExp(), 1248, req, remove, give, message, 1).run(); + } + + /** + * Display options. + * + * @param player + * the player + * @param item + * the item + */ + public static void displayOptions(Player player, Item item) { + ItemDef def = ItemDef.forId(item.getId()); + String name = def.getName().toUpperCase().replaceAll(" ", "_"); + fletchItem = FletchItem.valueOf(name); + if (fletchItem == null) { + return; + } + + int inter = (302 + (fletchItem.getItems().length - 1)); + int[] items = new int[fletchItem.getItems().length]; + for (int i = 0; i < items.length; i++) { + items[i] = fletchItem.getItems()[i].getId(); + } + new DisplaySelectOptionAction(player, inter, 175, items).run(); + } + + /** + * Gets the fletch item. + * + * @return the fletch item + */ + public static FletchItem getFletchItem() { + return fletchItem; + } + + /** + * The Enum FletchItem. + */ + private enum FletchItem { + + /* + * Arrow tips. + */ + BRONZE_ARROWTIPS(39, 1, 2.6, 882), IRON_ARROWTIPS(40, 15, 3.8, 884), STEEL_ARROWTIPS(41, 30, 6.3, 886), MITHRIL_ARROWTIPS(42, 45, 8.8, 888), ADAMANT_ARROWTIPS(43, 60, 11.3, 890), RUNE_ARROWTIPS(44, 75, 13.8, 892), DRAGON_ARROWTIPS(0, 90, 16.3, 11212), + + /* + * Misc. + */ + SHAFTS(52, 1, .33D, 53), + + /* + * Unstrung bows. + */ + SHORTBOW_U(50, 5, 5D, 841), LONGBOW_U(48, 10, 10D, 839), OAK_SHORTBOW_U(54, 20, 16.5D, 843), OAK_LONGBOW_U(56, 25, 25D, 845), WILLOW_SHORTBOW_U(60, 35, 33.3D, 849), WILLOW_LONGBOW_U(58, 40, 41.5D, 847), MAPLE_SHORTBOW_U(64, 50, 50D, 853), MAPLE_LONGBOW_U(62, 55, 58.5D, 851), YEW_SHORTBOW_U(68, 65, 67.5D, 857), YEW_LONGBOW_U(66, 70, 75D, 855), MAGIC_SHORTBOW_U(72, 80, 83.3D, 861), MAGIC_LONGBOW_U(70, 85, 91.5D, 859), + + /* + * Logs. + */ + LOGS(1511, SHAFTS, SHORTBOW_U, LONGBOW_U), OAK_LOGS(1521, OAK_SHORTBOW_U, OAK_LONGBOW_U), WILLOW_LOGS(1519, WILLOW_SHORTBOW_U, WILLOW_LONGBOW_U), MAPLE_LOGS(1517, MAPLE_SHORTBOW_U, MAPLE_LONGBOW_U), YEW_LOGS(1515, YEW_SHORTBOW_U, YEW_LONGBOW_U), MAGIC_LOGS(1513, MAGIC_SHORTBOW_U, MAGIC_LONGBOW_U); + + /** The req. */ + private int req; + + /** The exp. */ + private double exp; + + /** The completed. */ + private int completed; + + /** The id. */ + private int id; + + /** The items. */ + private FletchItem[] items; + + /** + * Instantiates a new fletch item. + * + * @param id + * the id + * @param items + * the items + */ + private FletchItem(int id, FletchItem... items) { + this.id = id; + this.items = items; + } + + /** + * Instantiates a new fletch item. + * + * @param id + * the id + * @param req + * the req + * @param exp + * the exp + * @param completed + * the completed + */ + private FletchItem(int id, int req, double exp, int completed) { + this.id = id; + this.req = req; + this.exp = exp; + this.completed = completed; + } + + /** + * Gets the id. + * + * @return the id + */ + public int getId() { + return id; + } + + /** + * Gets the req. + * + * @return the req + */ + public int getReq() { + return req; + } + + /** + * Gets the exp. + * + * @return the exp + */ + public double getExp() { + return exp; + } + + /** + * Gets the completed. + * + * @return the completed + */ + public int getCompleted() { + return completed; + } + + /** + * Gets the items. + * + * @return the items + */ + public FletchItem[] getItems() { + return items; + } + } +} diff --git a/src/osiris/game/model/skills/Mining.java b/src/osiris/game/model/skills/Mining.java new file mode 100644 index 0000000..28bc4d7 --- /dev/null +++ b/src/osiris/game/model/skills/Mining.java @@ -0,0 +1,278 @@ +package osiris.game.model.skills; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.util.HashMap; +import java.util.Map; + +/** + * @author Blake Beaupain + * + */ +public class Mining { + + /* + * TODO: Figure out a way to find object face and other things so we can + * actually have a specific type for each empty rock. For now, they will all + * share the same ID/face. + */ + + // Ore keywords. + public static final String COPPER_KEYWORD = "copper"; + public static final String TIN_KEYWORD = "tin"; + public static final String CLAY_KEYWORD = "clay"; + public static final String IRON_KEYWORD = "iron"; + public static final String COAL_KEYWORD = "coal"; + public static final String SILVER_KEYWORD = "silver"; + public static final String GOLD_KEYWORD = "gold"; + public static final String MITHRIL_KEYWORD = "mithril"; + public static final String ADAMANTITE_KEYWORD = "adamantite"; + public static final String RUNITE_KEYWORD = "runite"; + + public static final int BRONZE_SPEED = 2; + public static final int IRON_SPEED = 3; + public static final int STEEL_SPEED = 4; + public static final int MITHRIL_SPEED = 5; + public static final int ADAMANT_SPEED = 6; + public static final int RUNE_SPEED = 7; + + public static final int BRONZE_PICKAXE = 1265; + public static final int IRON_PICKAXE = 1267; + public static final int STEEL_PICKAXE = 1269; + public static final int MITHRIL_PICKAXE = 1273; + public static final int ADAMANT_PICKAXE = 1271; + public static final int RUNE_PICKAXE = 1275; + + public static final int BRONZE_ANIMATION = 625; + public static final int IRON_ANIMATION = 626; + public static final int STEEL_ANIMATION = 627; + public static final int MITHRIL_ANIMATION = 629; + public static final int ADAMANT_ANIMATION = 628; + public static final int RUNE_ANIMATION = 624; + + public static final int LEVEL_BRONZE_PICKAXE = 1; + public static final int LEVEL_IRON_PICKAXE = 1; + public static final int LEVEL_STEEL_PICKAXE = 6; + public static final int LEVEL_MITHRIL_PICKAXE = 21; + public static final int LEVEL_ADAMANT_PICKAXE = 31; + public static final int LEVEL_RUNE_PICKAXE = 41; + + public static final int LEVEL_COPPER = 1; + public static final int LEVEL_TIN = 1; + public static final int LEVEL_CLAY = 1; + public static final int LEVEL_IRON = 15; + public static final int LEVEL_SILVER = 20; + public static final int LEVEL_COAL = 30; + public static final int LEVEL_GOLD = 40; + public static final int LEVEL_MITHRIL = 55; + public static final int LEVEL_ADAMANTITE = 70; + public static final int LEVEL_RUNITE = 85; + + public static final int ORE_COPPER = 436; + public static final int ORE_TIN = 438; + public static final int ORE_CLAY = 434; + public static final int ORE_IRON = 440; + public static final int ORE_COAL = 453; + public static final int ORE_SILVER = 442; + public static final int ORE_GOLD = 444; + public static final int ORE_MITHRIL = 447; + public static final int ORE_ADAMANTITE = 449; + public static final int ORE_RUNITE = 451; + + public static final int EXP_CLAY = 5; + public static final int EXP_COPPER = 18; + public static final int EXP_TIN = 18; + public static final int EXP_IRON = 35; + public static final int EXP_SILVER = 40; + public static final int EXP_COAL = 50; + public static final int EXP_GOLD = 65; + public static final int EXP_MITHRIL = 80; + public static final int EXP_ADAMANTITE = 95; + public static final int EXP_RUNITE = 125; + + public static final int RESPAWN_CLAY = 1; + public static final int RESPAWN_TIN = 5; + public static final int RESPAWN_COPPER = 5; + public static final int RESPAWN_IRON = 10; + public static final int RESPAWN_SILVER = 120; + public static final int RESPAWN_COAL = 60; + public static final int RESPAWN_GOLD = 120; + public static final int RESPAWN_MITHRIL = 240; + public static final int RESPAWN_ADAMANTITE = 480; + public static final int RESPAWN_RUNITE = 1200; + + // Rock IDs + public static final int[] COPPER = { 11938, 11937, 11936, 11962, 11960, 11961 }; + public static final int[] TIN = { 11933, 11959, 11957, 11958, 11950, 11949, 11948, 11934, 11935 }; + public static final int[] CLAY = { 11556, 11557 }; + public static final int[] IRON = { 37309, 37308, 37307, 11955, 11954, 11956 }; + public static final int[] COAL = { 11932, 11930 }; + public static final int[] SILVER = { 37306, 37305, 37304 }; + public static final int[] GOLD = { 37310, 37312, 15505, 15503, 15504 }; + public static final int[] MITHRIL = { 11942, 11944 }; + public static final int[] ADAMANTITE = { 11939, 11941 }; + public static final int[] RUNITE = { 14860, 14859 }; + + private static final Map keywordMap = new HashMap(); + + private static final Map pickaxeLevelMap = new HashMap(); + + private static final Map animationMap = new HashMap(); + + private static final Map rockLevelMap = new HashMap(); + + private static final Map pickaxeSpeedMap = new HashMap(); + + private static final Map oreMap = new HashMap(); + + private static final Map expMap = new HashMap(); + + private static final Map respawnMap = new HashMap(); + + static { + // Map the keywords + for (int id : COPPER) + keywordMap.put(id, COPPER_KEYWORD); + for (int id : TIN) + keywordMap.put(id, TIN_KEYWORD); + for (int id : CLAY) + keywordMap.put(id, CLAY_KEYWORD); + for (int id : IRON) + keywordMap.put(id, IRON_KEYWORD); + for (int id : COAL) + keywordMap.put(id, COAL_KEYWORD); + for (int id : SILVER) + keywordMap.put(id, SILVER_KEYWORD); + for (int id : GOLD) + keywordMap.put(id, GOLD_KEYWORD); + for (int id : MITHRIL) + keywordMap.put(id, MITHRIL_KEYWORD); + for (int id : ADAMANTITE) + keywordMap.put(id, ADAMANTITE_KEYWORD); + for (int id : RUNITE) + keywordMap.put(id, RUNITE_KEYWORD); + + // Map the pickaxe levels. + pickaxeLevelMap.put(BRONZE_PICKAXE, LEVEL_BRONZE_PICKAXE); + pickaxeLevelMap.put(IRON_PICKAXE, LEVEL_IRON_PICKAXE); + pickaxeLevelMap.put(STEEL_PICKAXE, LEVEL_STEEL_PICKAXE); + pickaxeLevelMap.put(MITHRIL_PICKAXE, LEVEL_MITHRIL_PICKAXE); + pickaxeLevelMap.put(ADAMANT_PICKAXE, LEVEL_ADAMANT_PICKAXE); + pickaxeLevelMap.put(RUNE_PICKAXE, LEVEL_RUNE_PICKAXE); + + // Map the rock levels. + rockLevelMap.put(COPPER_KEYWORD, LEVEL_COPPER); + rockLevelMap.put(TIN_KEYWORD, LEVEL_TIN); + rockLevelMap.put(CLAY_KEYWORD, LEVEL_CLAY); + rockLevelMap.put(IRON_KEYWORD, LEVEL_IRON); + rockLevelMap.put(COAL_KEYWORD, LEVEL_COAL); + rockLevelMap.put(SILVER_KEYWORD, LEVEL_SILVER); + rockLevelMap.put(GOLD_KEYWORD, LEVEL_GOLD); + rockLevelMap.put(MITHRIL_KEYWORD, LEVEL_MITHRIL); + rockLevelMap.put(ADAMANTITE_KEYWORD, LEVEL_ADAMANTITE); + rockLevelMap.put(RUNITE_KEYWORD, LEVEL_RUNITE); + + // Mining animations + animationMap.put(BRONZE_PICKAXE, BRONZE_ANIMATION); + animationMap.put(IRON_PICKAXE, IRON_ANIMATION); + animationMap.put(STEEL_PICKAXE, STEEL_ANIMATION); + animationMap.put(MITHRIL_PICKAXE, MITHRIL_ANIMATION); + animationMap.put(ADAMANT_PICKAXE, ADAMANT_ANIMATION); + animationMap.put(RUNE_PICKAXE, RUNE_ANIMATION); + + // Pickaxe speeds + pickaxeSpeedMap.put(BRONZE_PICKAXE, BRONZE_SPEED); + pickaxeSpeedMap.put(IRON_PICKAXE, IRON_SPEED); + pickaxeSpeedMap.put(STEEL_PICKAXE, STEEL_SPEED); + pickaxeSpeedMap.put(MITHRIL_PICKAXE, MITHRIL_SPEED); + pickaxeSpeedMap.put(ADAMANT_PICKAXE, ADAMANT_SPEED); + pickaxeSpeedMap.put(RUNE_PICKAXE, RUNE_SPEED); + + // Ore obtained. + oreMap.put(COPPER_KEYWORD, ORE_COPPER); + oreMap.put(TIN_KEYWORD, ORE_TIN); + oreMap.put(CLAY_KEYWORD, ORE_CLAY); + oreMap.put(IRON_KEYWORD, ORE_IRON); + oreMap.put(COAL_KEYWORD, ORE_COAL); + oreMap.put(SILVER_KEYWORD, ORE_SILVER); + oreMap.put(GOLD_KEYWORD, ORE_GOLD); + oreMap.put(MITHRIL_KEYWORD, ORE_MITHRIL); + oreMap.put(ADAMANTITE_KEYWORD, ORE_ADAMANTITE); + oreMap.put(RUNITE_KEYWORD, ORE_RUNITE); + + // Experience obtained + expMap.put(COPPER_KEYWORD, EXP_COPPER); + expMap.put(TIN_KEYWORD, EXP_TIN); + expMap.put(CLAY_KEYWORD, EXP_CLAY); + expMap.put(IRON_KEYWORD, EXP_IRON); + expMap.put(COAL_KEYWORD, EXP_COAL); + expMap.put(SILVER_KEYWORD, EXP_SILVER); + expMap.put(GOLD_KEYWORD, EXP_GOLD); + expMap.put(MITHRIL_KEYWORD, EXP_MITHRIL); + expMap.put(ADAMANTITE_KEYWORD, EXP_ADAMANTITE); + expMap.put(RUNITE_KEYWORD, EXP_RUNITE); + + respawnMap.put(COPPER_KEYWORD, RESPAWN_COPPER); + respawnMap.put(TIN_KEYWORD, RESPAWN_TIN); + respawnMap.put(CLAY_KEYWORD, RESPAWN_CLAY); + respawnMap.put(IRON_KEYWORD, RESPAWN_IRON); + respawnMap.put(COAL_KEYWORD, RESPAWN_COAL); + respawnMap.put(SILVER_KEYWORD, RESPAWN_SILVER); + respawnMap.put(GOLD_KEYWORD, RESPAWN_GOLD); + respawnMap.put(MITHRIL_KEYWORD, RESPAWN_MITHRIL); + respawnMap.put(ADAMANTITE_KEYWORD, RESPAWN_ADAMANTITE); + respawnMap.put(RUNITE_KEYWORD, RESPAWN_RUNITE); + } + + public static int getDelay(int playerLevel, int pickaxeSpeed, int rockLevel) { + return (30 - playerLevel / 5) - pickaxeSpeed + rockLevel; + } + + public static Map getKeywordMap() { + return keywordMap; + } + + public static Map getPickaxeLevelMap() { + return pickaxeLevelMap; + } + + public static Map getRockLevelMap() { + return rockLevelMap; + } + + public static Map getAnimationMap() { + return animationMap; + } + + public static Map getPickaxeSpeedMap() { + return pickaxeSpeedMap; + } + + public static Map getOreMap() { + return oreMap; + } + + public static Map getExpMap() { + return expMap; + } + + public static Map getRespawnMap() { + return respawnMap; + } +} diff --git a/src/osiris/game/model/skills/Prayer.java b/src/osiris/game/model/skills/Prayer.java new file mode 100644 index 0000000..e589778 --- /dev/null +++ b/src/osiris/game/model/skills/Prayer.java @@ -0,0 +1,149 @@ +package osiris.game.model.skills; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * + * @author Boomer + * + */ +public enum Prayer { + + THICK_SKIN(1, 1, .05, Type.DEFENCE, 83), BURST_OF_SKIN(4, 1, .05, Type.STRENGTH, 84), CLARITY_OF_THOUGHT(7, 1, .05, Type.ATTACK, 85), SHARP_EYE(8, 1, .05, Type.RANGE, 862), MYSTIC_WILL(9, 1, .05, Type.MAGIC, 863), ROCK_SKIN(10, 2, .1, Type.DEFENCE, 86), SUPERHUMAN_STRENGTH(13, 2, .1, Type.STRENGTH, 87), IMPROVED_REFLEXES(16, 2, .1, Type.ATTACK, 88), RAPID_RESTORE(19, 1, .035, Type.RESTORE, 89), RAPID_HEAL(22, 2, .07, Type.RESTORE, 90), PROTECT_ITEM(25, 1, .07, Type.SALVAGE, 91), HAWK_EYE(26, 2, .1, Type.RANGE, 864), MYSTIC_LORE(27, 2, .1, Type.MAGIC, 865), STEEL_SKIN(28, 3, .2, Type.DEFENCE, 92), ULTIMATE_STRENGTH(31, 3, .2, Type.STRENGTH, 93), INCREDIBLE_REFLEXES(34, 3, .2, Type.ATTACK, 94), PROTECT_FROM_MAGIC(37, 1, .2, Type.HEADICON, 95, 2), PROTECT_FROM_MISSILES(40, 2, .2, + Type.HEADICON, 96, 1), PROTECT_FROM_MELEE(43, 3, .2, Type.HEADICON, 97, 0), EAGLE_EYE(44, 3, .2, Type.RANGE, 866), MYSTIC_MIGHT(45, 3, .2, Type.MAGIC, 867), RETRIBUTION(46, 4, .05, Type.HEADICON, 98, 3), REDEMPTION(49, 5, .1, Type.HEADICON, 99, 5), SMITE(52, 6, .2, Type.HEADICON, 100, 4), PROTECT_FROM_SUMMONING(35, 1, .2, Type.HEADICON, 1168), CHIVALRY(60, 1, .4, Type.BADASS, 1052), PIETY(70, 1, .4, Type.BADASS, 1053); + + /** The level required. */ + private int levelRequired, stage, headIcon, configId; + + /** The drain rate. */ + private double drainRate; + + /** The type. */ + private Type type; + + /** + * Instantiates a new prayer. + * + * @param levelRequired + * the level required + * @param stage + * the stage + * @param drainRate + * the drain rate + * @param type + * the type + * @param configId + * the config id + * @param headIcon + * the head icon + */ + Prayer(int levelRequired, int stage, double drainRate, Type type, int configId, int headIcon) { + this.levelRequired = levelRequired; + this.stage = stage; + this.drainRate = drainRate; + this.type = type; + this.headIcon = headIcon; + this.configId = configId; + } + + /** + * Instantiates a new prayer. + * + * @param levelRequired + * the level required + * @param stage + * the stage + * @param drainRate + * the drain rate + * @param type + * the type + * @param configId + * the config id + */ + Prayer(int levelRequired, int stage, double drainRate, Type type, int configId) { + this.levelRequired = levelRequired; + this.stage = stage; + this.drainRate = drainRate; + this.type = type; + this.configId = configId; + this.headIcon = -1; + + } + + /** + * Gets the level required. + * + * @return the level required + */ + public int getLevelRequired() { + return levelRequired; + } + + /** + * Gets the stage. + * + * @return the stage + */ + public int getStage() { + return stage; + } + + /** + * Gets the config id. + * + * @return the config id + */ + public int getConfigId() { + return configId; + } + + /** + * Gets the head icon. + * + * @return the head icon + */ + public int getHeadIcon() { + return headIcon; + } + + /** + * Gets the drain rate. + * + * @return the drain rate + */ + public double getDrainRate() { + return drainRate; + } + + /** + * Gets the type. + * + * @return the type + */ + public Type getType() { + return type; + } + + /** + * The Enum Type. + */ + public enum Type { + ATTACK, STRENGTH, DEFENCE, RANGE, MAGIC, RESTORE, SALVAGE, HEADICON, BADASS + } +} diff --git a/src/osiris/game/model/skills/Skill.java b/src/osiris/game/model/skills/Skill.java new file mode 100644 index 0000000..ee6aa6f --- /dev/null +++ b/src/osiris/game/model/skills/Skill.java @@ -0,0 +1,48 @@ +package osiris.game.model.skills; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * The Class Skill. + * + * @author Boomer + */ +public abstract class Skill { + + /** + * Can iterate. + * + * @return true, if successful + */ + public abstract boolean canIterate(); + + /** + * Calculate cooldown. + * + * @return the int + */ + public abstract int calculateCooldown(); + + /** + * On completion. + */ + public void onCompletion() { + } + +} diff --git a/src/osiris/game/model/skills/Smithing.java b/src/osiris/game/model/skills/Smithing.java new file mode 100644 index 0000000..da4adaf --- /dev/null +++ b/src/osiris/game/model/skills/Smithing.java @@ -0,0 +1,137 @@ +package osiris.game.model.skills; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.action.TickedAction; +import osiris.game.model.Player; +import osiris.game.model.Skills; +import osiris.game.model.item.Item; + +// TODO: Auto-generated Javadoc +/** + * The Class Smithing. + * + * @author Blakeman8192? + */ +public class Smithing { + + /** + * The Enum Bar. + */ + public enum Bar { + + BRONZE(2349, 1, new Item[] { new Item(436), new Item(438) }), IRON(2351, 15, new Item[] { new Item(440) }), SILVER(2355, 20, new Item[] { new Item(442) }), STEEL(2353, 30, new Item[] { new Item(440), new Item(453, 2) }), GOLD(2357, 40, new Item[] { new Item(444) }), MITHRIL(2359, 50, new Item[] { new Item(447), new Item(453, 4) }), ADAMANT(2361, 70, new Item[] { new Item(449), new Item(453, 6) }), RUNE(2363, 85, new Item[] { new Item(451), new Item(453, 8) }); + + /** The bar id. */ + private final int barId; + + /** The required level. */ + private final int requiredLevel; + + /** The required ore. */ + private final Item[] requiredOre; + + /** + * Instantiates a new bar. + * + * @param barId + * the bar id + * @param requiredLevel + * the required level + * @param requiredOre + * the required ore + */ + private Bar(int barId, int requiredLevel, Item[] requiredOre) { + this.barId = barId; + this.requiredLevel = requiredLevel; + this.requiredOre = requiredOre; + } + + /** + * Gets the bar id. + * + * @return the bar id + */ + public int getBarId() { + return barId; + } + + /** + * Gets the required level. + * + * @return the required level + */ + public int getRequiredLevel() { + return requiredLevel; + } + + /** + * Gets the required ore. + * + * @return the required ore + */ + public Item[] getRequiredOre() { + return requiredOre; + } + } + + /** + * Smelt. + * + * @param player + * the player + * @param bar + * the bar + * @param amount + * the amount + */ + public static void smelt(final Player player, final Bar bar, final int amount) { + if (player.getSkills().currentLevel(Skills.SKILL_SMITHING) < bar.requiredLevel) { + player.getEventWriter().sendMessage("You need a smithing level of " + bar.requiredLevel + " to smelt this."); + } + + new TickedAction(player, 4) { + private int ticks = 0; + + /* + * (non-Javadoc) + * + * @see osiris.game.action.TickedAction#execute() + */ + @Override + public void execute() { + if (ticks++ == amount) { + cancel(); + return; + } + Item[] ore = bar.requiredOre; + for (Item item : ore) { + if (player.getInventory().amountOfItem(item.getId()) < item.getAmount()) { + cancel(); + return; + } + player.getInventory().removeById(item.getId(), item.getAmount()); + } + player.getEventWriter().sendMessage("You smelt a " + bar.toString() + " bar."); + player.getInventory().add(new Item(bar.getBarId())); + } + }.run(); + } + +} diff --git a/src/osiris/game/model/skills/Woodcutting.java b/src/osiris/game/model/skills/Woodcutting.java new file mode 100644 index 0000000..1a83f90 --- /dev/null +++ b/src/osiris/game/model/skills/Woodcutting.java @@ -0,0 +1,318 @@ +package osiris.game.model.skills; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.util.HashMap; +import java.util.Map; + +/** + * @author Blake Beaupain + * + */ +public class Woodcutting { + + // Axe speed constants + public static final int BRONZE_SPEED = 1; + public static final int IRON_SPEED = 1; + public static final int STEEL_SPEED = 2; + public static final int BLACK_SPEED = 3; + public static final int MITHRIL_SPEED = 4; + public static final int ADAMANT_SPEED = 5; + public static final int RUNE_SPEED = 6; + public static final int DRAGON_SPEED = 7; + + // Axe constants + public static final int BRONZE_AXE = 1351; + public static final int IRON_AXE = 1349; + public static final int STEEL_AXE = 1353; + public static final int BLACK_AXE = 1361; + public static final int MITHRIL_AXE = 1355; + public static final int ADAMANT_AXE = 1357; + public static final int RUNE_AXE = 1359; + public static final int DRAGON_AXE = 6739; + + // Tree constants + public static final int TREE_1 = 1278; + public static final int TREE_2 = 1276; + public static final int TREE_3 = 1315; + public static final int TREE_4 = 1316; + public static final int TREE_OAK = 1281; + public static final int TREE_WILLOW_1 = 1308; + public static final int TREE_WILLOW_2 = 5551; + public static final int TREE_WILLOW_3 = 5552; + public static final int TREE_WILLOW_4 = 5553; + public static final int TREE_MAPLE = 1307; + public static final int TREE_YEW = 1309; + public static final int TREE_MAGIC = 1306; + + // Animation constants + public static final int BRONZE_ANIMATION = 879; + public static final int IRON_ANIMATION = 877; + public static final int STEEL_ANIMATION = 875; + public static final int BLACK_ANIMATION = 873; + public static final int MITHRIL_ANIMATION = 871; + public static final int ADAMANT_ANIMATION = 869; + public static final int RUNE_ANIMATION = 867; + public static final int DRAGON_ANIMATION = 2846; + + // Axe level constants + public static final int LEVEL_BRONZE = 1; + public static final int LEVEL_IRON = 1; + public static final int LEVEL_STEEL = 5; + public static final int LEVEL_BLACK = 10; + public static final int LEVEL_MITHRIL = 21; + public static final int LEVEL_ADAMANT = 31; + public static final int LEVEL_RUNE = 41; + public static final int LEVEL_DRAGON = 61; + + // Tree level constants. + public static final int LEVEL_TREE = 1; + public static final int LEVEL_OAK = 15; + public static final int LEVEL_WILLOW = 30; + public static final int LEVEL_MAPLE = 45; + public static final int LEVEL_YEW = 60; + public static final int LEVEL_MAGIC = 75; + + // Tree fall chances - the closer to 1.0, the higher the chance of falling + // per log cut. + public static final double CHANCE_TREE = 1; + public static final double CHANCE_OAK = .33; + public static final double CHANCE_WILLOW = .2; + public static final double CHANCE_MAPLE = .33; + public static final double CHANCE_YEW = .15; + public static final double CHANCE_MAGIC = .15; + + // Logs - logs obtained from trees. + public static final int LOGS_TREE = 1511; + public static final int LOGS_OAK = 1521; + public static final int LOGS_WILLOW = 1519; + public static final int LOGS_MAPLE = 1517; + public static final int LOGS_YEW = 1515; + public static final int LOGS_MAGIC = 1513; + + // Experience gained from each log from each tree. + public static final int EXP_TREE = 25; + public static final int EXP_OAK = 38; + public static final int EXP_WILLOW = 68; + public static final int EXP_MAPLE = 100; + public static final int EXP_YEW = 175; + public static final int EXP_MAGIC = 250; + + // Tree stumps + public static final int STUMP_TREE = 4822; + public static final int STUMP_OAK = 1356; + public static final int STUMP_WILLOW = 7399; + public static final int STUMP_MAPLE = 7400; + public static final int STUMP_YEW = 7402; + public static final int STUMP_MAGIC = 7401; + + // Tree respawn delays + public static final int DELAY_TREE = 30; + public static final int DELAY_OAK = 13; + public static final int DELAY_WILLOW = 13; + public static final int DELAY_MAPLE = 60; + public static final int DELAY_YEW = 98; + public static final int DELAY_MAGIC = 190; + + // A map of axe speeds. + private static final Map axeSpeedMap = new HashMap(); + + // A map of axe levels. + private static final Map axeLevelMap = new HashMap(); + + // A map of tree levels. + private static final Map treeLevelMap = new HashMap(); + + // A map of chopping animations. + private static final Map animationMap = new HashMap(); + + // A map of tree-fall chances. + private static final Map treeChanceMap = new HashMap(); + + // Logs map + private static final Map logsMap = new HashMap(); + + // Map for experience gained from trees. + private static final Map experienceMap = new HashMap(); + + // Tree stumps + private static final Map stumpMap = new HashMap(); + + // Tree respawn delays + private static final Map respawnDelayMap = new HashMap(); + + public static int getDelay(int playerLevel, int axeSpeed, int treeLevel) { + return (30 - playerLevel / 5) - axeSpeed + treeLevel; + } + + static { + // Maybe load these from a config file? + + // Axe speeds + axeSpeedMap.put(BRONZE_AXE, BRONZE_SPEED); + axeSpeedMap.put(IRON_AXE, IRON_SPEED); + axeSpeedMap.put(STEEL_AXE, STEEL_SPEED); + axeSpeedMap.put(BLACK_AXE, BLACK_SPEED); + axeSpeedMap.put(MITHRIL_AXE, MITHRIL_SPEED); + axeSpeedMap.put(ADAMANT_AXE, ADAMANT_SPEED); + axeSpeedMap.put(RUNE_AXE, RUNE_SPEED); + axeSpeedMap.put(DRAGON_AXE, DRAGON_SPEED); + + // Axe levels + axeLevelMap.put(BRONZE_AXE, LEVEL_BRONZE); + axeLevelMap.put(IRON_AXE, LEVEL_IRON); + axeLevelMap.put(STEEL_AXE, LEVEL_STEEL); + axeLevelMap.put(BLACK_AXE, LEVEL_BLACK); + axeLevelMap.put(MITHRIL_AXE, LEVEL_MITHRIL); + axeLevelMap.put(ADAMANT_AXE, LEVEL_ADAMANT); + axeLevelMap.put(RUNE_AXE, LEVEL_RUNE); + axeLevelMap.put(DRAGON_AXE, LEVEL_DRAGON); + + // Tree levels + treeLevelMap.put(TREE_1, LEVEL_TREE); + treeLevelMap.put(TREE_2, LEVEL_TREE); + treeLevelMap.put(TREE_3, LEVEL_TREE); + treeLevelMap.put(TREE_4, LEVEL_TREE); + treeLevelMap.put(TREE_OAK, LEVEL_OAK); + treeLevelMap.put(TREE_WILLOW_1, LEVEL_WILLOW); + treeLevelMap.put(TREE_WILLOW_2, LEVEL_WILLOW); + treeLevelMap.put(TREE_WILLOW_3, LEVEL_WILLOW); + treeLevelMap.put(TREE_WILLOW_4, LEVEL_WILLOW); + treeLevelMap.put(TREE_MAPLE, LEVEL_MAPLE); + treeLevelMap.put(TREE_YEW, LEVEL_YEW); + treeLevelMap.put(TREE_MAGIC, LEVEL_MAGIC); + + // Animation constants + animationMap.put(BRONZE_AXE, BRONZE_ANIMATION); + animationMap.put(IRON_AXE, IRON_ANIMATION); + animationMap.put(STEEL_AXE, STEEL_ANIMATION); + animationMap.put(BLACK_AXE, BLACK_ANIMATION); + animationMap.put(MITHRIL_AXE, MITHRIL_ANIMATION); + animationMap.put(ADAMANT_AXE, ADAMANT_ANIMATION); + animationMap.put(RUNE_AXE, RUNE_ANIMATION); + animationMap.put(DRAGON_AXE, DRAGON_ANIMATION); + + // Tree fall chances + treeChanceMap.put(TREE_1, CHANCE_TREE); + treeChanceMap.put(TREE_2, CHANCE_TREE); + treeChanceMap.put(TREE_3, CHANCE_TREE); + treeChanceMap.put(TREE_4, CHANCE_TREE); + treeChanceMap.put(TREE_OAK, CHANCE_OAK); + treeChanceMap.put(TREE_WILLOW_1, CHANCE_WILLOW); + treeChanceMap.put(TREE_WILLOW_2, CHANCE_WILLOW); + treeChanceMap.put(TREE_WILLOW_3, CHANCE_WILLOW); + treeChanceMap.put(TREE_WILLOW_4, CHANCE_WILLOW); + treeChanceMap.put(TREE_MAPLE, CHANCE_MAPLE); + treeChanceMap.put(TREE_YEW, CHANCE_YEW); + treeChanceMap.put(TREE_MAGIC, CHANCE_MAGIC); + + // Logs obtained from trees. + logsMap.put(TREE_1, LOGS_TREE); + logsMap.put(TREE_2, LOGS_TREE); + logsMap.put(TREE_3, LOGS_TREE); + logsMap.put(TREE_4, LOGS_TREE); + logsMap.put(TREE_OAK, LOGS_OAK); + logsMap.put(TREE_WILLOW_1, LOGS_WILLOW); + logsMap.put(TREE_WILLOW_2, LOGS_WILLOW); + logsMap.put(TREE_WILLOW_3, LOGS_WILLOW); + logsMap.put(TREE_WILLOW_4, LOGS_WILLOW); + logsMap.put(TREE_MAPLE, LOGS_MAPLE); + logsMap.put(TREE_YEW, LOGS_YEW); + logsMap.put(TREE_MAGIC, LOGS_MAGIC); + + // Experience from trees. + experienceMap.put(TREE_1, EXP_TREE); + experienceMap.put(TREE_2, EXP_TREE); + experienceMap.put(TREE_3, EXP_TREE); + experienceMap.put(TREE_4, EXP_TREE); + experienceMap.put(TREE_OAK, EXP_OAK); + experienceMap.put(TREE_WILLOW_1, EXP_WILLOW); + experienceMap.put(TREE_WILLOW_2, EXP_WILLOW); + experienceMap.put(TREE_WILLOW_3, EXP_WILLOW); + experienceMap.put(TREE_WILLOW_4, EXP_WILLOW); + experienceMap.put(TREE_MAPLE, EXP_MAPLE); + experienceMap.put(TREE_YEW, EXP_YEW); + experienceMap.put(TREE_MAGIC, EXP_MAGIC); + + // Tree stumps + stumpMap.put(TREE_1, STUMP_TREE); + stumpMap.put(TREE_2, STUMP_TREE); + stumpMap.put(TREE_3, STUMP_TREE); + stumpMap.put(TREE_4, STUMP_TREE); + stumpMap.put(TREE_OAK, STUMP_OAK); + stumpMap.put(TREE_WILLOW_1, STUMP_WILLOW); + stumpMap.put(TREE_WILLOW_2, STUMP_WILLOW); + stumpMap.put(TREE_WILLOW_3, STUMP_WILLOW); + stumpMap.put(TREE_WILLOW_4, STUMP_WILLOW); + stumpMap.put(TREE_MAPLE, STUMP_MAPLE); + stumpMap.put(TREE_YEW, STUMP_YEW); + stumpMap.put(TREE_MAGIC, STUMP_MAGIC); + + // Tree respawn delays. + respawnDelayMap.put(TREE_1, DELAY_TREE); + respawnDelayMap.put(TREE_2, DELAY_TREE); + respawnDelayMap.put(TREE_3, DELAY_TREE); + respawnDelayMap.put(TREE_4, DELAY_TREE); + respawnDelayMap.put(TREE_OAK, DELAY_OAK); + respawnDelayMap.put(TREE_WILLOW_1, DELAY_WILLOW); + respawnDelayMap.put(TREE_WILLOW_2, DELAY_WILLOW); + respawnDelayMap.put(TREE_WILLOW_3, DELAY_WILLOW); + respawnDelayMap.put(TREE_WILLOW_4, DELAY_WILLOW); + respawnDelayMap.put(TREE_MAPLE, DELAY_MAPLE); + respawnDelayMap.put(TREE_YEW, DELAY_YEW); + respawnDelayMap.put(TREE_MAGIC, DELAY_MAGIC); + } + + public static Map getAxeSpeedMap() { + return axeSpeedMap; + } + + public static Map getAxeLevelMap() { + return axeLevelMap; + } + + public static Map getTreeLevelMap() { + return treeLevelMap; + } + + public static Map getAnimationMap() { + return animationMap; + } + + public static Map getTreeChanceMap() { + return treeChanceMap; + } + + public static Map getLogsMap() { + return logsMap; + } + + public static Map getExperienceMap() { + return experienceMap; + } + + public static Map getStumpMap() { + return stumpMap; + } + + public static Map getRespawnDelayMap() { + return respawnDelayMap; + } + +} diff --git a/src/osiris/game/update/NpcUpdater.java b/src/osiris/game/update/NpcUpdater.java new file mode 100644 index 0000000..a9b525e --- /dev/null +++ b/src/osiris/game/update/NpcUpdater.java @@ -0,0 +1,208 @@ +package osiris.game.update; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import osiris.Main; +import osiris.game.model.Direction; +import osiris.game.model.Npc; +import osiris.game.model.Player; +import osiris.game.update.UpdateFlags.UpdateFlag; +import osiris.io.PacketHeader; +import osiris.io.PacketWriter; +import osiris.io.PacketHeader.LengthType; +import osiris.io.PacketWriter.AccessType; + +// TODO: Auto-generated Javadoc + +/** + * Updates NPCs for a player. + * + * @author Blake + * + */ +public class NpcUpdater extends PlayerUpdater { + + /** + * Instantiates a new npc updater. + * + * @param player + * the player + */ + public NpcUpdater(Player player) { + super(player); + } + + /* + * (non-Javadoc) + * + * @see osiris.game.update.PlayerUpdater#cycle() + */ + + @Override + public void run() { + try { + Player player = getPlayer(); + PacketWriter writer = new PacketWriter(new PacketHeader(222, LengthType.VARIABLE_SHORT)); + PacketWriter blockWriter = new PacketWriter(); + + writer.setAccessType(AccessType.BIT_ACCESS); + + // Update local NPCs. + writer.writeBits(8, player.getLocalNpcs().size()); + for (Iterator it = player.getLocalNpcs().iterator(); it.hasNext();) { + Npc other = it.next(); + if (Main.getNpcs().contains(other) && player.isInSight(other) && !other.getUpdateFlags().isFlagged(UpdateFlag.TELEPORTED) && other.isVisible()) { + UpdateFlags f = other.getUpdateFlags(); + if (!f.isFlagged(UpdateFlag.UPDATE)) { + writer.writeBit(false); + } else { + writer.writeBit(true); + updateMovement(other, writer); + if (other.hasUpdateBlocks()) { + updateBlocks(other, blockWriter); + } + } + } else { + it.remove(); + writer.writeBit(true); + writer.writeBits(2, 3); + other.getLocalPlayers().remove(player); + } + } + + // Update the local NPC list itself. + int addedCount = 0; + for (Npc npc : Main.getNpcs()) { + if (addedCount == 15 || player.getLocalNpcs().size() >= 255) { + break; + } + if (!npc.isVisible()) { + continue; + } + if (player.getLocalNpcs().contains(npc)) { + continue; + } + if (player.isInSight(npc)) { + addedCount++; + player.getLocalNpcs().add(npc); + addNpc(player, npc, writer); + npc.getLocalPlayers().add(player); + if (npc.hasUpdateBlocks()) { + updateBlocks(npc, blockWriter); + } + } + } + + if (blockWriter.getPacket().getBuffer().writerIndex() > 0) { + writer.writeBits(15, 32767); + writer.setAccessType(AccessType.BYTE_ACCESS); + writer.getPacket().getBuffer().writeBytes(blockWriter.getPacket().getBuffer()); + } else { + writer.setAccessType(AccessType.BYTE_ACCESS); + } + + player.getChannel().write(writer.getPacket()); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + /** + * Adds the npc. + * + * @param player + * the player + * @param npc + * the npc + * @param writer + * the writer + */ + private void addNpc(Player player, Npc npc, PacketWriter writer) { + int deltaX = npc.getPosition().getX() - player.getPosition().getX(); + int deltaY = npc.getPosition().getY() - player.getPosition().getY(); + if (deltaX < 0) { + deltaX += 32; + } + if (deltaY < 0) { + deltaY += 32; + } + + writer.writeBits(15, npc.getSlot()); + writer.writeBits(14, npc.getId()); + writer.writeBit(npc.hasUpdateBlocks()); + writer.writeBits(5, deltaY); + writer.writeBits(5, deltaX); + writer.writeBits(3, 0); + writer.writeBit(true); + } + + /** + * Update movement. + * + * @param npc + * the npc + * @param writer + * the writer + */ + private void updateMovement(Npc npc, PacketWriter writer) { + if (npc.getPrimaryDirection() == Direction.NONE) { + writer.writeBits(2, 0); + } else { + writer.writeBits(2, 1); + writer.writeBits(3, npc.getPrimaryDirection().toInteger()); + writer.writeBit(npc.hasUpdateBlocks()); + } + } + + /** + * Update blocks. + * + * @param npc + * the npc + * @param writer + * the writer + * @throws Exception + * the exception + */ + private void updateBlocks(Npc npc, PacketWriter writer) throws Exception { + List blocks = npc.getUpdateBlocks(); + + // Sort the update blocks. + Collections.sort(blocks, UpdateBlockComparator.getSingleton()); + + // Compile the mask value. + int mask = 0; + for (UpdateBlock block : blocks) { + mask |= block.getMask(); + } + + // Write the mask value. + writer.writeByte(mask); + + // Append each block. + for (UpdateBlock block : blocks) { + block.append(writer); + } + } + +} diff --git a/src/osiris/game/update/PlayerUpdater.java b/src/osiris/game/update/PlayerUpdater.java new file mode 100644 index 0000000..fdf68e7 --- /dev/null +++ b/src/osiris/game/update/PlayerUpdater.java @@ -0,0 +1,304 @@ +package osiris.game.update; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import osiris.Main; +import osiris.game.model.Direction; +import osiris.game.model.Player; +import osiris.game.model.Position; +import osiris.game.update.UpdateFlags.UpdateFlag; +import osiris.game.update.block.AppearanceBlock; +import osiris.io.ByteForm; +import osiris.io.PacketHeader; +import osiris.io.PacketWriter; +import osiris.io.PacketHeader.LengthType; +import osiris.io.PacketWriter.AccessType; + +/** + * Updates a Player. + * + * @author Blake + * + */ +public class PlayerUpdater implements Runnable { + + /** + * The player. + */ + private final Player player; + + /** + * Instantiates a new player updater. + * + * @param player + * the player + */ + public PlayerUpdater(Player player) { + this.player = player; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Runnable#cycle() + */ + + @Override + public void run() { + try { + // Update the new map region if necessary. + Position current = player.getPosition(); + Position last = player.getLastRegion(); + int deltaX = current.getX() - (last.getRegionX() - 6) * 8; + int deltaY = current.getY() - (last.getRegionY() - 6) * 8; + if (!(deltaX >= 16 && deltaX < 88 && deltaY >= 16 && deltaY < 88)) { + player.getEventWriter().sendLandscape(); + player.getUpdateFlags().flag(UpdateFlag.TELEPORTED); + player.getUpdateFlags().flag(UpdateFlag.NO_MOVEMENT_QUEUE_RESET); + player.setLastRegion(current); + } + + PacketWriter writer = new PacketWriter(new PacketHeader(216, LengthType.VARIABLE_SHORT)); + PacketWriter blockWriter = new PacketWriter(); + UpdateFlags f = player.getUpdateFlags(); + + writer.setAccessType(AccessType.BIT_ACCESS); + + // Update this player. + if (!f.isFlagged(UpdateFlag.UPDATE)) { + writer.writeBit(false); + } else { + writer.writeBit(true); + updateMyMovement(player, writer); + if (player.hasUpdateBlocks()) { + updateBlocks(player, blockWriter, false); + } + } + + // Update local players. + writer.writeBits(8, player.getLocalPlayers().size()); + for (Iterator it = player.getLocalPlayers().iterator(); it.hasNext();) { + Player other = it.next(); + if (Main.getPlayers().contains(other) && player.isInSight(other) && !other.getUpdateFlags().isFlagged(UpdateFlag.TELEPORTED) && other.isVisible()) { + UpdateFlags oF = other.getUpdateFlags(); + if (!oF.isFlagged(UpdateFlag.UPDATE)) { + writer.writeBit(false); + } else { + writer.writeBit(true); + updateMovement(other, writer); + if (other.hasUpdateBlocks()) { + updateBlocks(other, blockWriter, false); + } + } + } else { + it.remove(); + writer.writeBit(true); + writer.writeBits(2, 3); + } + } + + int addedCount = 0; + for (Player other : Main.getPlayers()) { + if (addedCount == 15 || player.getLocalPlayers().size() >= 255) { + break; + } + if (player.getLocalPlayers().contains(other) || other == player || !other.isVisible()) { + continue; + } + if (player.isInSight(other)) { + addedCount++; + player.getLocalPlayers().add(other); + addPlayer(player, other, writer); + updateBlocks(other, blockWriter, true); + } + } + + if (blockWriter.getPacket().getBuffer().writerIndex() > 0) { + writer.writeBits(11, 2047); + writer.setAccessType(AccessType.BYTE_ACCESS); + writer.getPacket().getBuffer().writeBytes(blockWriter.getPacket().getBuffer()); + } else { + writer.setAccessType(AccessType.BYTE_ACCESS); + } + + player.getChannel().write(writer.getPacket()); + writer = null; + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + /** + * Adds the player. + * + * @param player + * the player + * @param other + * the other + * @param writer + * the writer + */ + private void addPlayer(Player player, Player other, PacketWriter writer) { + int deltaX = other.getPosition().getX() - player.getPosition().getX(); + int deltaY = other.getPosition().getY() - player.getPosition().getY(); + if (deltaX < 0) { + deltaX += 32; + } + if (deltaY < 0) { + deltaY += 32; + } + writer.writeBits(11, other.getSlot()); + writer.writeBits(5, deltaX); + writer.writeBit(true); + writer.writeBits(3, 1); + writer.writeBit(true); + writer.writeBits(5, deltaY); + } + + /** + * Update blocks. + * + * @param player + * the player + * @param writer + * the writer + * @param forceAppearance + * the force appearance + * @throws Exception + * the exception + */ + private void updateBlocks(Player player, PacketWriter writer, boolean forceAppearance) throws Exception { + List blocks = player.getUpdateBlocks(); + + // Check if we need to force appearance. + if (forceAppearance) { + boolean needsAppearanceBlock = true; + for (UpdateBlock block : blocks) { + if (block instanceof AppearanceBlock) { + needsAppearanceBlock = false; + break; + } + } + + // Force the block if it's not present. + if (needsAppearanceBlock) { + // / Rebuild a new list to keep updating read-only and thread + // safe. + blocks = new LinkedList(); + blocks.addAll(player.getUpdateBlocks()); + blocks.add(new AppearanceBlock(player)); + } + } + + // Sort the update blocks. + Collections.sort(blocks, UpdateBlockComparator.getSingleton()); + + // Compile the mask value. + int mask = 0; + for (UpdateBlock block : blocks) { + mask |= block.getMask(); + } + + // Write the mask value. + if (mask >= 0x100) { + mask |= 0x10; + writer.writeShort(mask, ByteForm.LITTLE); + } else { + writer.writeByte(mask); + } + + // Append each block. + for (UpdateBlock block : blocks) { + // System.out.println("Appending update block: " + block); + block.append(writer); + } + // System.out.println("END OF UPDATE BLOCKS"); + } + + /** + * Update movement. + * + * @param player + * the player + * @param writer + * the writer + */ + private void updateMovement(Player player, PacketWriter writer) { + if (player.getPrimaryDirection() == Direction.NONE) { + writer.writeBits(2, 0); + } else if (player.getSecondaryDirection() == Direction.NONE) { + writer.writeBits(2, 1); + writer.writeBits(3, player.getPrimaryDirection().toInteger()); + writer.writeBit(player.hasUpdateBlocks()); + } else { + writer.writeBits(2, 2); + writer.writeBits(3, player.getPrimaryDirection().toInteger()); + writer.writeBits(3, player.getSecondaryDirection().toInteger()); + writer.writeBit(player.hasUpdateBlocks()); + } + } + + /** + * Update my movement. + * + * @param player + * the player + * @param writer + * the writer + */ + private void updateMyMovement(Player player, PacketWriter writer) { + UpdateFlags f = player.getUpdateFlags(); + if (f.isFlagged(UpdateFlag.TELEPORTED)) { + writer.writeBits(2, 3); + writer.writeBits(7, player.getPosition().getLocalX(player.getLastRegion())); + writer.writeBit(!player.getUpdateFlags().isFlagged(UpdateFlag.NO_MOVEMENT_QUEUE_RESET)); + writer.writeBits(2, player.getPosition().getZ()); + writer.writeBit(player.hasUpdateBlocks()); + writer.writeBits(7, player.getPosition().getLocalY(player.getLastRegion())); + } else { + if (player.getPrimaryDirection() == Direction.NONE) { + writer.writeBits(2, 0); + } else if (player.getSecondaryDirection() == Direction.NONE) { + writer.writeBits(2, 1); + writer.writeBits(3, player.getPrimaryDirection().toInteger()); + writer.writeBit(player.hasUpdateBlocks()); + } else { + writer.writeBits(2, 2); + writer.writeBits(3, player.getPrimaryDirection().toInteger()); + writer.writeBits(3, player.getSecondaryDirection().toInteger()); + writer.writeBit(player.hasUpdateBlocks()); + } + } + } + + /** + * Gets the player. + * + * @return the player + */ + public Player getPlayer() { + return player; + } + +} diff --git a/src/osiris/game/update/UpdateBlock.java b/src/osiris/game/update/UpdateBlock.java new file mode 100644 index 0000000..844395f --- /dev/null +++ b/src/osiris/game/update/UpdateBlock.java @@ -0,0 +1,115 @@ +package osiris.game.update; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.model.Character; +import osiris.io.PacketWriter; + +// TODO: Auto-generated Javadoc + +/** + * Represents a block of the update packet. + * + * @author Blake + * + */ +public abstract class UpdateBlock { + + /** + * The character. + */ + private final Character character; + + /** + * The mask. + */ + private final int mask; + + /** + * The priority. + */ + private final int priority; + + /** + * Instantiates a new update block. + * + * @param character + * the character + * @param mask + * the mask + * @param priority + * the priority + */ + public UpdateBlock(Character character, int mask, int priority) { + this.character = character; + this.mask = mask; + this.priority = priority; + } + + /** + * Appends the block to the argued packet writer. + * + * @param writer + * the writer + * @throws Exception + * the exception + */ + public abstract void append(PacketWriter writer) throws Exception; + + /* + * (non-Javadoc) + * + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object other) { + if (other instanceof UpdateBlock) { + return ((UpdateBlock) other).getMask() == getMask(); + } + return false; + } + + /** + * Gets the character. + * + * @return the character + */ + public Character getCharacter() { + return character; + } + + /** + * Gets the mask. + * + * @return the mask + */ + public int getMask() { + return mask; + } + + /** + * Gets the priority. + * + * @return the priority + */ + public int getPriority() { + return priority; + } + +} diff --git a/src/osiris/game/update/UpdateBlockComparator.java b/src/osiris/game/update/UpdateBlockComparator.java new file mode 100644 index 0000000..a55bdb1 --- /dev/null +++ b/src/osiris/game/update/UpdateBlockComparator.java @@ -0,0 +1,54 @@ +package osiris.game.update; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.util.Comparator; + +/** + * Used for update block sorting. + * + * @author Blake + * + */ +public class UpdateBlockComparator implements Comparator { + + /** The Constant singleton. */ + private static final UpdateBlockComparator singleton = new UpdateBlockComparator(); + + /** + * Gets the singleton. + * + * @return the singleton + */ + public static UpdateBlockComparator getSingleton() { + return singleton; + } + + /* + * (non-Javadoc) + * + * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) + */ + + @Override + public int compare(UpdateBlock o1, UpdateBlock o2) { + return o1.getPriority() - o2.getPriority(); + } + +} diff --git a/src/osiris/game/update/UpdateFlags.java b/src/osiris/game/update/UpdateFlags.java new file mode 100644 index 0000000..e5b6649 --- /dev/null +++ b/src/osiris/game/update/UpdateFlags.java @@ -0,0 +1,113 @@ +package osiris.game.update; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.util.BitSet; + +// TODO: Auto-generated Javadoc + +/** + * Update flags. + * + * @author Blake + * + */ +public class UpdateFlags { + + /** + * The flags. + */ + private BitSet flags = new BitSet(); + + /** + * The Enum UpdateFlag. + */ + public enum UpdateFlag { + + /** + * An update. + */ + UPDATE(0), + + /** + * A teleport update. + */ + TELEPORTED(1), + + /** + * No movement queue reset. + */ + NO_MOVEMENT_QUEUE_RESET(2); + + /** + * The id. + */ + private int id; + + /** + * Instantiates a new update flag. + * + * @param id + * the id + */ + UpdateFlag(int id) { + this.id = id; + } + + /** + * Gets the id. + * + * @return the id + */ + public int getId() { + return id; + } + + } + + /** + * Flags an update flag. + * + * @param flag + * the flag + */ + public void flag(UpdateFlag flag) { + flags.set(flag.getId(), true); + flags.set(UpdateFlag.UPDATE.getId(), true); + } + + /** + * Checks if a flag is flagged. + * + * @param flag + * the flag + * @return true, if the flag is flagged + */ + public boolean isFlagged(UpdateFlag flag) { + return flags.get(flag.getId()); + } + + /** + * Reset. + */ + public void reset() { + flags.clear(); + } + +} diff --git a/src/osiris/game/update/block/AnimationBlock.java b/src/osiris/game/update/block/AnimationBlock.java new file mode 100644 index 0000000..0cacfe0 --- /dev/null +++ b/src/osiris/game/update/block/AnimationBlock.java @@ -0,0 +1,89 @@ +package osiris.game.update.block; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.model.Character; +import osiris.game.model.Npc; +import osiris.game.update.UpdateBlock; +import osiris.io.PacketWriter; +import osiris.io.ValueType; + +// TODO: Auto-generated Javadoc + +/** + * An update block for animations. + * + * @author Blake + * + */ +public class AnimationBlock extends UpdateBlock { + + /** + * The id. + */ + private final int id; + + /** + * The delay. + */ + private final int delay; + + /** + * Instantiates a new animation block. + * + * @param character + * the character + * @param id + * the id + * @param delay + * the delay + */ + public AnimationBlock(Character character, int id, int delay) { + super(character, 0x1, (character instanceof Npc ? 3 : 7)); + this.id = id; + this.delay = delay; + } + + /* + * (non-Javadoc) + * + * @see osiris.game.update.UpdateBlock#append(osiris.io.PacketWriter) + */ + + @Override + public void append(PacketWriter writer) throws Exception { + if (getCharacter() instanceof Npc) { + writer.writeShort(id, ValueType.A); + writer.writeByte(delay); + } else { + writer.writeShort(id); + writer.writeByte(delay, ValueType.S); + } + } + + /** + * Gets the animation id. + * + * @return the animation id + */ + public int getAnimationId() { + return id; + } + +} diff --git a/src/osiris/game/update/block/AppearanceBlock.java b/src/osiris/game/update/block/AppearanceBlock.java new file mode 100644 index 0000000..c263a66 --- /dev/null +++ b/src/osiris/game/update/block/AppearanceBlock.java @@ -0,0 +1,189 @@ +package osiris.game.update.block; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import static osiris.util.Settings.SLOT_CHEST; +import static osiris.util.Settings.SLOT_FEET; +import static osiris.util.Settings.SLOT_HANDS; +import static osiris.util.Settings.SLOT_HAT; +import static osiris.util.Settings.SLOT_LEGS; +import static osiris.util.Settings.SLOT_SHIELD; +import osiris.game.model.Appearance; +import osiris.game.model.Player; +import osiris.game.model.def.ItemDef; +import osiris.game.model.item.ItemContainer; +import osiris.game.update.UpdateBlock; +import osiris.io.PacketWriter; +import osiris.util.Utilities; + +/** + * An appearance update block. + * + * @author Blake + * + */ +public class AppearanceBlock extends UpdateBlock { + + /** The Constant STAND_INDICE. */ + public static final int STAND_INDICE = 0x328; + + /** The Constant WALK_INDICE. */ + public static final int WALK_INDICE = 0x333; + + /** The Constant RUN_INDICE. */ + public static final int RUN_INDICE = 0x338; + + /** + * Instantiates a new appearance block. + * + * @param player + * the player + */ + public AppearanceBlock(Player player) { + super(player, 0x80, 8); + } + + /* + * (non-Javadoc) + * + * @see osiris.game.update.UpdateBlock#append(osiris.io.PacketWriter) + */ + + @Override + public void append(PacketWriter writer) throws Exception { + Player player = (Player) getCharacter(); + ItemContainer e = player.getEquipment(); + Appearance a = player.getAppearance(); + PacketWriter p = new PacketWriter(); + + // Send the initial attributes. + p.writeByte(a.getGender()); + if ((a.getGender() & 0x2) == 2) { + p.writeByte(0); + p.writeByte(0); + } + p.writeByte(player.getSkullIcon()); // skull + p.writeByte(player.getHeadIcon()); // prayer + + if (player.getNpcId() == -1) { + for (int i = 0; i < 4; i++) { + if (e.getItem(i) != null) { + p.writeShort(32768 + ItemDef.forId(e.getItem(i).getId()).getEquipId()); + } else { + p.writeByte(0); + } + } + + // Chest slot + if (e.getItem(SLOT_CHEST) != null) { + p.writeShort(32768 + ItemDef.forId(e.getItem(SLOT_CHEST).getId()).getEquipId()); + } else { + p.writeShort(0x100 + a.getLooks()[Appearance.LOOK_CHEST]); + } + + // Shield slot + if (e.getItem(SLOT_SHIELD) != null) { + p.writeShort(32768 + ItemDef.forId(e.getItem(SLOT_SHIELD).getId()).getEquipId()); + } else { + p.writeByte(0); + } + + // Arms slot + if (e.getItem(SLOT_CHEST) != null) { + if (!ItemDef.forId(e.getItem(SLOT_CHEST).getId()).isFullBody()) { + p.writeShort(0x100 + a.getLooks()[Appearance.LOOK_ARMS]); + } else { + p.writeByte(0); + } + } else { + p.writeShort(0x100 + a.getLooks()[Appearance.LOOK_ARMS]); + } + + // Legs slot + if (e.getItem(SLOT_LEGS) != null) { + p.writeShort(32768 + ItemDef.forId(e.getItem(SLOT_LEGS).getId()).getEquipId()); + } else { + p.writeShort(0x100 + a.getLooks()[Appearance.LOOK_LEGS]); + } + + // Head slot + if (e.getItem(SLOT_HAT) != null) { + if (ItemDef.forId(e.getItem(SLOT_HAT).getId()).isFullHelm() || ItemDef.forId(e.getItem(SLOT_HAT).getId()).isFullMask()) { + p.writeByte(0); + } else { + p.writeShort(0x100 + a.getLooks()[Appearance.LOOK_HEAD]); + } + } else { + p.writeShort(0x100 + a.getLooks()[Appearance.LOOK_HEAD]); + } + + // Hands slot + if (e.getItem(SLOT_HANDS) != null) { + p.writeShort(32768 + ItemDef.forId(e.getItem(SLOT_HANDS).getId()).getEquipId()); + } else { + p.writeShort(0x100 + a.getLooks()[Appearance.LOOK_HANDS]); + } + + // Feet slot + if (e.getItem(SLOT_FEET) != null) { + p.writeShort(32768 + ItemDef.forId(e.getItem(SLOT_FEET).getId()).getEquipId()); + } else { + p.writeShort(0x100 + a.getLooks()[Appearance.LOOK_FEET]); + } + + // Beard slot + if (e.getItem(SLOT_HAT) != null) { + if (ItemDef.forId(e.getItem(SLOT_HAT).getId()).isFullMask()) { + p.writeByte(0); + } else { + p.writeShort(0x100 + a.getLooks()[Appearance.LOOK_BEARD]); + } + } else { + p.writeShort(0x100 + a.getLooks()[Appearance.LOOK_BEARD]); + } + } else { + p.writeShort(-1); + p.writeShort(player.getNpcId()); + } + + // Colors + for (int color : a.getColors()) { + p.writeByte(color); + } + + // Animation indices + p.writeShort(player.getStandAnimation()); + p.writeShort(0x337); + p.writeShort(player.getWalkAnimation()); + p.writeShort(0x334); + p.writeShort(0x335); + p.writeShort(0x336); + p.writeShort(player.getRunAnimation()); + + // Other misc attributes + p.writeLong(Utilities.stringToLong(player.getUsername())); + p.writeByte(player.getSkills().getCombatLevel()); // Combat + // level. + p.writeShort(0); + + // Write the props packet. + writer.writeByte(p.getPacket().getBuffer().writerIndex()); + writer.getPacket().getBuffer().writeBytes(p.getPacket().getBuffer()); + } +} diff --git a/src/osiris/game/update/block/ChatBlock.java b/src/osiris/game/update/block/ChatBlock.java new file mode 100644 index 0000000..2c12e78 --- /dev/null +++ b/src/osiris/game/update/block/ChatBlock.java @@ -0,0 +1,94 @@ +package osiris.game.update.block; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.model.Player; +import osiris.game.update.UpdateBlock; +import osiris.io.PacketWriter; +import osiris.io.ValueType; + +// TODO: Auto-generated Javadoc + +/** + * The chat update block. + * + * @author Blake + * + */ +public class ChatBlock extends UpdateBlock { + + /** + * The effects. + */ + private final int effects; + + /** + * The player status. + */ + private final int playerStatus; + + /** + * The data. + */ + private final byte[] data; + + /** + * The encryption offset. + */ + private final int encryptionOffset; + + /** + * Instantiates a new chat block. + * + * @param player + * the player + * @param effects + * the effects + * @param playerStatus + * the player status + * @param data + * the data + * @param encryptionOffset + * the encryption offset + */ + public ChatBlock(Player player, int effects, int playerStatus, byte[] data, int encryptionOffset) { + super(player, 0x8, 6); + this.effects = effects; + this.playerStatus = playerStatus; + this.data = data; + this.encryptionOffset = encryptionOffset; + } + + /* + * (non-Javadoc) + * + * @see osiris.game.update.UpdateBlock#append(osiris.io.PacketWriter) + */ + + @Override + public void append(PacketWriter writer) throws Exception { + // TODO MUTING + writer.writeShort(effects, ValueType.A); + writer.writeByte(playerStatus, ValueType.C); + writer.writeByte(encryptionOffset, ValueType.C); + for (int i = 0; i < encryptionOffset; i++) { + writer.writeByte(data[i]); + } + } +} diff --git a/src/osiris/game/update/block/FaceToCharacterBlock.java b/src/osiris/game/update/block/FaceToCharacterBlock.java new file mode 100644 index 0000000..c366ea5 --- /dev/null +++ b/src/osiris/game/update/block/FaceToCharacterBlock.java @@ -0,0 +1,64 @@ +package osiris.game.update.block; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.model.Character; +import osiris.game.model.Npc; +import osiris.game.update.UpdateBlock; +import osiris.io.PacketWriter; + +// TODO: Auto-generated Javadoc + +/** + * The block to make a player face to a certain coordinate?. + * + * @author Blake + * + */ +public class FaceToCharacterBlock extends UpdateBlock { + + /** + * The face to. + */ + private final int faceTo; + + /** + * Instantiates a new face to block. + * + * @param character + * the character + * @param faceTo + * the face to + */ + public FaceToCharacterBlock(Character character, int faceTo) { + super(character, (character instanceof Npc ? 0x10 : 0x20), (character instanceof Npc ? 0 : 2)); + this.faceTo = faceTo; + } + + /* + * (non-Javadoc) + * + * @see osiris.game.update.UpdateBlock#append(osiris.io.PacketWriter) + */ + @Override + public void append(PacketWriter writer) throws Exception { + writer.writeShort(faceTo); + } + +} diff --git a/src/osiris/game/update/block/FaceToPositionBlock.java b/src/osiris/game/update/block/FaceToPositionBlock.java new file mode 100644 index 0000000..9e19d21 --- /dev/null +++ b/src/osiris/game/update/block/FaceToPositionBlock.java @@ -0,0 +1,67 @@ +package osiris.game.update.block; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.model.Character; +import osiris.game.model.Npc; +import osiris.game.model.Position; +import osiris.game.update.UpdateBlock; +import osiris.io.ByteForm; +import osiris.io.PacketWriter; +import osiris.io.ValueType; + +/** + * @author Boomer + * + */ +public class FaceToPositionBlock extends UpdateBlock { + + /** The position. */ + private Position position; + + /** + * Instantiates a new update block. + * + * @param character + * the character + * @param position + * the position + */ + public FaceToPositionBlock(Character character, Position position) { + super(character, (character instanceof Npc ? 0x80 : 0x40), (character instanceof Npc ? 6 : 5)); + this.position = position; + } + + /* + * (non-Javadoc) + * + * @see osiris.game.update.UpdateBlock#append(osiris.io.PacketWriter) + */ + @Override + public void append(PacketWriter writer) throws Exception { + if (getCharacter() instanceof Npc) { + writer.writeShort((position.getX() * 2) + 1, ValueType.A); + writer.writeShort((position.getY() * 2) + 1, ValueType.A, ByteForm.LITTLE); + } else { + writer.writeShort((position.getX() * 2) + 1, ByteForm.LITTLE); + writer.writeShort((position.getY() * 2) + 1, ValueType.A); + } + } + +} \ No newline at end of file diff --git a/src/osiris/game/update/block/ForcedChatBlock.java b/src/osiris/game/update/block/ForcedChatBlock.java new file mode 100644 index 0000000..14da5d0 --- /dev/null +++ b/src/osiris/game/update/block/ForcedChatBlock.java @@ -0,0 +1,57 @@ +package osiris.game.update.block; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.model.Character; +import osiris.game.model.Npc; +import osiris.game.update.UpdateBlock; +import osiris.io.PacketWriter; + +/** + * @author Boomer + * + */ +public class ForcedChatBlock extends UpdateBlock { + + /** The chat. */ + private String chat; + + /** + * Instantiates a new update block. + * + * @param character + * the character + * @param chat + * the chat + */ + public ForcedChatBlock(Character character, String chat) { + super(character, character instanceof Npc ? 0x40 : 0x4, character instanceof Npc ? 2 : 3); + this.chat = chat; + } + + /* + * (non-Javadoc) + * + * @see osiris.game.update.UpdateBlock#append(osiris.io.PacketWriter) + */ + @Override + public void append(PacketWriter writer) throws Exception { + writer.writeString(chat); + } +} diff --git a/src/osiris/game/update/block/GraphicsBlock.java b/src/osiris/game/update/block/GraphicsBlock.java new file mode 100644 index 0000000..414afec --- /dev/null +++ b/src/osiris/game/update/block/GraphicsBlock.java @@ -0,0 +1,84 @@ +package osiris.game.update.block; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.model.Character; +import osiris.game.model.Npc; +import osiris.game.update.UpdateBlock; +import osiris.io.ByteForm; +import osiris.io.PacketWriter; +import osiris.io.ValueType; + +/** + * @author Blake + * + */ +public class GraphicsBlock extends UpdateBlock { + + /** The id. */ + private final int id; + + /** The height. */ + private final int height; + + /** + * Instantiates a new graphics block. + * + * @param character + * the character + * @param id + * the id + * @param height + * the height + */ + public GraphicsBlock(Character character, int id, int height) { + super(character, (character instanceof Npc ? 0x2 : 0x400), (character instanceof Npc ? 4 : 4)); + this.id = id; + if (height >= 100) + height += 6553500; + this.height = height; + } + + /* + * (non-Javadoc) + * + * @see osiris.game.update.UpdateBlock#append(osiris.io.PacketWriter) + */ + + @Override + public void append(PacketWriter writer) throws Exception { + if (getCharacter() instanceof Npc) { + writer.writeShort(id, ValueType.A); // graphics id + writer.writeInt(height, ByteForm.INVERSE_MIDDLE); + } else { + writer.writeShort(id); + writer.writeInt(height, ByteForm.MIDDLE); + } + } + + /** + * Gets the id. + * + * @return the id + */ + public int getId() { + return id; + } + +} diff --git a/src/osiris/game/update/block/PrimaryHitBlock.java b/src/osiris/game/update/block/PrimaryHitBlock.java new file mode 100644 index 0000000..2449f15 --- /dev/null +++ b/src/osiris/game/update/block/PrimaryHitBlock.java @@ -0,0 +1,86 @@ +package osiris.game.update.block; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.model.Character; +import osiris.game.model.Npc; +import osiris.game.update.UpdateBlock; +import osiris.io.PacketWriter; +import osiris.io.ValueType; + +// TODO: Auto-generated Javadoc + +/** + * The update block for primary damage hits. + * + * @author Blake + * + */ +public class PrimaryHitBlock extends UpdateBlock { + + /** The damage. */ + private final int damage; + + /** The type. */ + private final int type; + + /** + * Instantiates a new primary hit block. + * + * @param character + * the character + * @param damage + * the damage + * @param type + * the type + */ + public PrimaryHitBlock(Character character, int damage, int type) { + super(character, (character instanceof Npc ? 0x4 : 0x2), (character instanceof Npc ? 7 : 9)); + this.damage = damage; + this.type = type; + } + + /* + * (non-Javadoc) + * + * @see osiris.game.update.UpdateBlock#append(osiris.io.PacketWriter) + */ + + @Override + public void append(PacketWriter writer) throws Exception { + int hp = getCharacter().getCurrentHp(); + if (hp < 0) { + hp = 0; + } + int hpRatio = hp * 255 / getCharacter().getMaxHp(); + if (hpRatio > 255) { + hpRatio = 255; + } + if (getCharacter() instanceof Npc) { + writer.writeByte(damage); + writer.writeByte(type); + writer.writeByte(hpRatio, ValueType.S); + } else { + writer.writeByte(damage, ValueType.S); + writer.writeByte(type, ValueType.S); + writer.writeByte(hpRatio, ValueType.S); + } + } + +} diff --git a/src/osiris/game/update/block/SecondaryHitBlock.java b/src/osiris/game/update/block/SecondaryHitBlock.java new file mode 100644 index 0000000..8893b49 --- /dev/null +++ b/src/osiris/game/update/block/SecondaryHitBlock.java @@ -0,0 +1,74 @@ +package osiris.game.update.block; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.model.Character; +import osiris.game.model.Npc; +import osiris.game.update.UpdateBlock; +import osiris.io.PacketWriter; +import osiris.io.ValueType; + +/** + * The secondary damage hit block. + * + * @author Blake + * + */ +public class SecondaryHitBlock extends UpdateBlock { + + /** The amount. */ + private final int damage; + + /** The type. */ + private final int type; + + /** + * Instantiates a new secondary hit block. + * + * @param character + * the character + * @param damage + * the damage + * @param type + * the type + */ + public SecondaryHitBlock(Character character, int damage, int type) { + super(character, (character instanceof Npc ? 0x20 : 0x200), (character instanceof Npc ? 5 : 1)); + this.type = type; + this.damage = damage; + } + + /* + * (non-Javadoc) + * + * @see osiris.update.UpdateBlock#append(osiris.io.PacketWriter) + */ + + @Override + public void append(PacketWriter writer) throws Exception { + if (getCharacter() instanceof Npc) { + writer.writeByte(damage); // damage amount + writer.writeByte(type, ValueType.S); + } else { + writer.writeByte(damage, ValueType.S); // damage amount + writer.writeByte(type, ValueType.A); + } + } + +} diff --git a/src/osiris/game/update/block/TransformNpcBlock.java b/src/osiris/game/update/block/TransformNpcBlock.java new file mode 100644 index 0000000..69de6d6 --- /dev/null +++ b/src/osiris/game/update/block/TransformNpcBlock.java @@ -0,0 +1,56 @@ +package osiris.game.update.block; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.game.model.Character; +import osiris.game.update.UpdateBlock; +import osiris.io.ByteForm; +import osiris.io.PacketWriter; + +/** + * @author Boomer + * + */ +public class TransformNpcBlock extends UpdateBlock { + + private int toNpcId; + + /** + * Instantiates a new update block. + * + * @param character + * the character + * @param toNpcId + * the npc id transofrming into + */ + public TransformNpcBlock(Character character, int toNpcId) { + super(character, 0x8, 1); + this.toNpcId = toNpcId; + } + + /* + * (non-Javadoc) + * + * @see osiris.game.update.UpdateBlock#append(osiris.io.PacketWriter) + */ + @Override + public void append(PacketWriter writer) throws Exception { + writer.writeShort(toNpcId, ByteForm.LITTLE); + } +} diff --git a/src/osiris/io/ByteForm.java b/src/osiris/io/ByteForm.java new file mode 100644 index 0000000..3805135 --- /dev/null +++ b/src/osiris/io/ByteForm.java @@ -0,0 +1,49 @@ +package osiris.io; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * The different types of byte forms. + * + * @author Blake + * + */ +public enum ByteForm { + + /** + * Big-endian + */ + BIG, + + /** + * Middle-endian + */ + MIDDLE, + + /** + * Inverse-middle-endian + */ + INVERSE_MIDDLE, + + /** + * Little-endian + */ + LITTLE + +} diff --git a/src/osiris/io/EventWriter.java b/src/osiris/io/EventWriter.java new file mode 100644 index 0000000..66d0bb3 --- /dev/null +++ b/src/osiris/io/EventWriter.java @@ -0,0 +1,909 @@ +package osiris.io; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.awt.Color; + +import org.jboss.netty.channel.ChannelFuture; +import org.jboss.netty.channel.ChannelFutureListener; + +import osiris.game.model.Character; +import osiris.game.model.Player; +import osiris.game.model.Position; +import osiris.game.model.WorldObject; +import osiris.game.model.item.Item; +import osiris.game.model.item.ItemContainer; +import osiris.io.PacketHeader.LengthType; +import osiris.util.LandscapeKeys; +import osiris.util.Settings; +import osiris.util.Utilities; + +// TODO: Auto-generated Javadoc +/** + * A class that writes game events to packets. + * + * @author Blake + * @author Boomer + * @author samuraiblood2 + * + */ +public class EventWriter { + + /** + * The player. + */ + private final Player player; + + /** + * Instantiates a new event writer. + * + * @param player + * the player + */ + public EventWriter(Player player) { + this.player = player; + } + + /** + * Sends an Npc's model to an interface. + * + * @param id + * the interface id. + * @param child + * the child id. + * @param npc + * the npc id. + */ + public void sendInterfaceNpc(int id, int child, int npc) { + PacketWriter writer = new PacketWriter(new PacketHeader(6)); + writer.writeShort(id, ByteForm.LITTLE); + writer.writeShort(child, ByteForm.LITTLE); + writer.writeShort(npc, ByteForm.LITTLE); + player.getChannel().write(writer.getPacket()); + } + + /** + * Send's an animation to an interface. + * + * @param id + * the interface id. + * @param child + * the child id. + * @param emote + * the emote id. + */ + public void sendInterfaceAnimation(int id, int child, int emote) { + PacketWriter writer = new PacketWriter(new PacketHeader(245)); + writer.writeShort(id, ByteForm.LITTLE); + writer.writeShort(child, ByteForm.LITTLE); + writer.writeShort(emote); + player.getChannel().write(writer.getPacket()); + } + + /** + * Send chat options. + * + * @param publicChat + * the public chat + * @param privateChat + * the private chat + * @param tradeChat + * the trade chat + */ + public void sendChatOptions(int publicChat, int privateChat, int tradeChat) { + PacketWriter writer = new PacketWriter(new PacketHeader(186)); + writer.writeByte(publicChat); + writer.writeByte(privateChat); + writer.writeByte(tradeChat); + player.getChannel().write(writer.getPacket()); + } + + /** + * Spawn gfx. + * + * @param id + * the id + * @param height + * the height + * @param position + * the position + */ + public void spawnGfx(int id, int height, Position position) { + sendPosition(position); + PacketWriter writer = new PacketWriter(new PacketHeader(248)); + writer.writeByte(0); + writer.writeShort(id); + writer.writeByte(height); + writer.writeShort(0); + player.getChannel().write(writer.getPacket()); + } + + /** + * Send interface item. + * + * @param inter + * the inter + * @param child + * the child + * @param size + * the size + * @param item + * the item + */ + public void sendInterfaceItem(int inter, int child, int size, int item) { + PacketWriter writer = new PacketWriter(new PacketHeader(35)); + writer.writeInt(((inter * 65536) + child), ByteForm.INVERSE_MIDDLE); + writer.writeInt(size, ByteForm.LITTLE); + writer.writeShort(item, ValueType.A, ByteForm.LITTLE); + player.getChannel().write(writer.getPacket()); + } + + /** + * Send projectile. + * + * @param from + * the from + * @param to + * the to + * @param graphic + * the graphic + * @param angle + * the angle + * @param startHeight + * the start height + * @param endHeight + * the end height + * @param speed + * the speed + * @param lockOn + * the lock on + */ + public void sendProjectile(Position from, Position to, int graphic, int angle, int startHeight, int endHeight, int speed, Character lockOn) { + sendPosition(from, -3, -2); + PacketWriter writer = new PacketWriter(new PacketHeader(112)); + writer.writeByte((byte) angle); + int offsetX = (from.getX() - to.getX()) * -1; + int offsetY = (from.getY() - to.getY()) * -1; + writer.writeByte((byte) offsetX); + writer.writeByte((byte) offsetY); + writer.writeShort((lockOn instanceof Player ? lockOn.getSlot() : lockOn.getSlot()), ValueType.C, ByteForm.BIG); + writer.writeShort(graphic); + writer.writeByte((byte) startHeight); + writer.writeByte((byte) endHeight); + writer.writeShort(51); + writer.writeShort(speed); + writer.writeByte((byte) 16); + writer.writeByte((byte) 64); + player.getChannel().write(writer.getPacket()); + } + + /** + * Send destroy ground item. + * + * @param item + * the item + * @param position + * the position + */ + public void sendDestroyGroundItem(Item item, Position position) { + int id = item.getId(); + sendPosition(position); + PacketWriter writer = new PacketWriter(new PacketHeader(201)); + writer.writeByte(0); + writer.writeShort(id); + player.getChannel().write(writer.getPacket()); + } + + /** + * Send create ground item. + * + * @param item + * the item + * @param position + * the position + */ + public void sendCreateGroundItem(Item item, Position position) { + sendPosition(position); + PacketWriter writer = new PacketWriter(new PacketHeader(25)); + writer.writeShort(item.getAmount(), ValueType.A, ByteForm.LITTLE); + writer.writeByte(0); + writer.writeShort(item.getId(), ValueType.A, ByteForm.LITTLE); + player.getChannel().write(writer.getPacket()); + } + + /** + * Restore gameframe. + */ + public void restoreGameframe() { + for (int b = 16; b <= 21; b++) + sendInterfaceConfig(548, b, false); + for (int a = 32; a <= 38; a++) + sendInterfaceConfig(548, a, false); + sendInterfaceConfig(548, 14, false); + sendInterfaceConfig(548, 31, false); + sendInterfaceConfig(548, 63, false); + sendInterfaceConfig(548, 72, false); + } + + /** + * Send friends status. + * + * @param i + * the i + */ + public void sendFriendsStatus(int i) { + PacketWriter writer = new PacketWriter(new PacketHeader(115)); + writer.writeByte(i); + player.getChannel().write(writer.getPacket()); + } + + /** + * Send a private message. + * + * @param name + * the name + * @param message + * the message + */ + public void sendSentPrivateMessage(long name, String message) { + byte[] bytes = new byte[message.length()]; + Utilities.encryptPlayerChat(bytes, 0, 0, message.length(), message.getBytes()); + + PacketWriter writer = new PacketWriter(new PacketHeader(89, LengthType.VARIABLE_BYTE)); + writer.writeLong(name); + writer.writeByte(message.length()); + writer.writeBytes(bytes); + player.getChannel().write(writer.getPacket()); + } + + /** + * Send a received private message. + * + * @param name + * the name + * @param rights + * the rights + * @param message + * the message + */ + public void sendReceivedPrivateMessage(long name, int rights, String message) { + int messages = player.getContacts().getMessages(); + byte[] bytes = new byte[message.length() + 1]; + bytes[0] = (byte) message.length(); + Utilities.encryptPlayerChat(bytes, 0, 1, message.length(), message.getBytes()); + + PacketWriter writer = new PacketWriter(new PacketHeader(178, LengthType.VARIABLE_BYTE)); + writer.writeLong(name); + writer.writeShort(1); + writer.writeBytes(new byte[] { (byte) ((messages << 16) & 0xFF), (byte) ((messages << 8) & 0xFF), (byte) (messages & 0xFF) }); + writer.writeByte(rights); + writer.writeBytes(bytes); + player.getChannel().write(writer.getPacket()); + } + + /** + * Send a friend. + * + * @param name + * the name + * @param world + * the world + */ + public void sendFriend(long name, int world) { + PacketWriter writer = new PacketWriter(new PacketHeader(154, LengthType.VARIABLE_BYTE)); + writer.writeLong(name); + writer.writeShort(world); + writer.writeByte(1); + if (world == 0) { + writer.writeString("Offline"); + } else if (world == 1) { + writer.writeString("Online"); + } + player.getChannel().write(writer.getPacket()); + } + + /** + * Send a list of ignores. + * + * @param names + * the names + */ + public void sendIgnores(Long[] names) { + PacketWriter writer = new PacketWriter(new PacketHeader(240, LengthType.VARIABLE_SHORT)); + for (int i = 0; i < names.length; i++) { + writer.writeLong(names[i]); + } + player.getChannel().write(writer.getPacket()); + } + + /** + * Send energy. + */ + public void sendEnergy() { + PacketWriter writer = new PacketWriter(new PacketHeader(99)); + writer.writeByte(player.getEnergy()); + player.getChannel().write(writer.getPacket()); + } + + /** + * Sets the object. + * + * @param object + * the new object + */ + public void setObject(WorldObject object) { + sendPosition(object.getObjectPosition()); + PacketWriter writer = new PacketWriter(new PacketHeader(30)); + writer.writeShort(object.getObjectID(), ByteForm.LITTLE); + writer.writeByte(0, ValueType.A); + writer.writeByte((object.getObjectType() << 2) + (object.getObjectFace() & 3), ValueType.C); + player.getChannel().write(writer.getPacket()); + } + + /** + * Send position. + * + * @param position + * the position + */ + public void sendPosition(Position position) { + sendPosition(position, 0, 0); + } + + /** + * Send position. + * + * @param position + * the position + * @param horizontalOffset + * the horizontal offset + * @param verticalOffset + * the vertical offset + */ + public void sendPosition(Position position, int horizontalOffset, int verticalOffset) { + PacketWriter writer = new PacketWriter(new PacketHeader(177)); + writer.writeByte(position.getLocalY(player.getLastRegion()) + verticalOffset); + writer.writeByte(position.getLocalX(player.getLastRegion()) + horizontalOffset, ValueType.S); + player.getChannel().write(writer.getPacket()); + } + + /** + * Send player option. + * + * @param option + * the option + * @param slot + * the slot + * @param top + * the top + */ + public void sendPlayerOption(String option, int slot, boolean top) { + PacketWriter writer = new PacketWriter(new PacketHeader(252, LengthType.VARIABLE_BYTE)); + writer.writeByte(top ? 1 : 0, ValueType.C); + writer.writeString(option); + writer.writeByte(slot, ValueType.C); + player.getChannel().write(writer.getPacket()); + } + + /** + * Send a window pane. + * + * @param id + * the id + */ + public void sendWindowPane(int id) { + PacketWriter writer = new PacketWriter(new PacketHeader(239)); + writer.writeShort(id); + writer.writeByte(0, ValueType.A); + player.getChannel().write(writer.getPacket()); + } + + /** + * Sets a tab to an interface tab. + * + * @param tabId + * the tab id + * @param childId + * the child id + */ + public void sendTab(int tabId, int childId) { + // TODO HD + sendInterface(1, childId == 137 ? 752 : 548, tabId, childId); + } + + /** + * Sends a message. + * + * @param message + * the message + */ + public void sendMessage(String message) { + PacketWriter writer = new PacketWriter(new PacketHeader(218, LengthType.VARIABLE_BYTE)); + writer.writeString(message); + player.getChannel().write(writer.getPacket()); + } + + /** + * Sends an interface. + * + * @param showId + * the show id + * @param windowId + * the window id + * @param interfaceId + * the interface id + * @param childId + * the child id + */ + public void sendInterface(int showId, int windowId, int interfaceId, int childId) { + PacketWriter writer = new PacketWriter(new PacketHeader(93)); + writer.writeShort(childId); + writer.writeByte(showId, ValueType.A); + writer.writeShort(windowId); + writer.writeShort(interfaceId); + player.getChannel().write(writer.getPacket()); + } + + /** + * Send close chatbox interface. + */ + public void sendCloseChatboxInterface() { + PacketWriter packet = new PacketWriter(new PacketHeader(246)); + packet.writeShort(752); + packet.writeShort(12); + player.getChannel().write(packet.getPacket()); + player.setInterfaceOpen(-1); + } + + /** + * Send chatbox interface. + * + * @param childId + * the child id + */ + public void sendChatboxInterface(int childId) { + sendInterface(0, 752, 12, childId); + player.setInterfaceOpen(752); + } + + /** + * Send close interface. + */ + public void sendCloseInterface() { + + sendInterface(1, 548, 8, 56); + player.setInterfaceOpen(-1); + } + + /** + * Send close inventory interface. + */ + public void sendCloseInventoryInterface() { + /* + * if(player.isHd()) { sendInterfaceConfig(746, 71, true); } else { + */ + sendInterfaceConfig(548, 71, true); + // } + } + + /** + * Send interface. + * + * @param id + * the id + */ + public void sendInterface(int id) { + sendCloseInterface(); + /* + * if(player.isHd()) { sendInterface(0, 746, isInventoryInterface ? 4 : + * 6, id); // 3 norm, 4 makes bank work, 6 makes help work + * sendInterface(0, 746, 8, id); } else { + */ + sendInterface(0, 548, 8, id); + player.setInterfaceOpen(id); + // } + } + + /** + * Send inventory interface. + * + * @param childId + * the child id + */ + public void sendInventoryInterface(int childId) { + /* + * if(player.isHd()) { sendInterfaceConfig(746, 71, false); } else { + */ + // } + /* + * if(player.isHd()) { sendInterface(0, 746, 71, childId); } else { + */ + sendInterface(0, 548, 69, childId); + updateTabs(false); + + // } + } + + /** + * Send bank options. + */ + public void sendBankOptions() { + PacketWriter writer = new PacketWriter(new PacketHeader(223)); + writer.writeShort(496); + writer.writeShort(0, ValueType.A, ByteForm.LITTLE); + writer.writeShort(73, ByteForm.LITTLE); + writer.writeShort(762, ByteForm.LITTLE); + writer.writeShort(1278, ByteForm.LITTLE); + writer.writeShort(20, ByteForm.LITTLE); + player.getChannel().write(writer.getPacket()); + /* + * Object[] inventoryOptions = new Object[]{"", "", "", "", "Sell X", + * "Sell 10", "Sell 5", "Sell 1", "Value", -1, 0, 7, 4, 0, (149 << 16)}; + * runScript(150, inventoryOptions, "IviiiIsssssssss"); + * setAccessMask(1278, 0, 149, 0, 28); + */ + writer = new PacketWriter(new PacketHeader(223)); + writer.writeShort(27); + writer.writeShort(0, ValueType.A, ByteForm.LITTLE); + writer.writeShort(0, ByteForm.LITTLE); + writer.writeShort(763, ByteForm.LITTLE); + writer.writeShort(1150, ByteForm.LITTLE); + writer.writeShort(18, ByteForm.LITTLE); + player.getChannel().write(writer.getPacket()); + sendString(Utilities.toProperCase(player.getUsername()) + "'s Bank", 762, 24); + runScript(1451, ""); + } + + /** + * Send interface config. + * + * @param interfaceId + * the interface id + * @param childId + * the child id + * @param set + * the set + */ + public void sendInterfaceConfig(int interfaceId, int childId, boolean set) { + PacketWriter writer = new PacketWriter(new PacketHeader(59)); + writer.writeByte(set ? 1 : 0, ValueType.C); + writer.writeShort(childId); + writer.writeShort(interfaceId); + player.getChannel().write(writer.getPacket()); + } + + /** + * Send config. + * + * @param id + * the id + * @param value + * the value + */ + + public void sendConfig(int id, int value) { + if (value < 128) { + sendConfig1(id, value); + } else { + sendConfig2(id, value); + } + } + + /** + * Send config1. + * + * @param id + * the id + * @param value + * the value + */ + public void sendConfig1(int id, int value) { + PacketWriter writer = new PacketWriter(new PacketHeader(100)); + writer.writeShort(id, ValueType.A); + writer.writeByte(value, ValueType.A); + player.getChannel().write(writer.getPacket()); + } + + /** + * Send config2. + * + * @param id + * the id + * @param value + * the value + */ + public void sendConfig2(int id, int value) { + PacketWriter writer = new PacketWriter(new PacketHeader(161)); + writer.writeShort(id); + writer.writeInt(value, ByteForm.MIDDLE); + player.getChannel().write(writer.getPacket()); + } + + /** + * Send landscape. + */ + public void sendLandscape() { + PacketWriter writer = new PacketWriter(new PacketHeader(142, LengthType.VARIABLE_SHORT)); + boolean forceSend = true; + if ((((player.getPosition().getRegionX() / 8) == 48) || ((player.getPosition().getRegionX() / 8) == 49)) && ((player.getPosition().getRegionY() / 8) == 48)) { + forceSend = false; + } + if (((player.getPosition().getRegionX() / 8) == 48) && ((player.getPosition().getRegionY() / 8) == 148)) { + forceSend = false; + } + writer.writeShort(player.getPosition().getRegionX(), ValueType.A); + writer.writeShort(player.getPosition().getLocalY(), ValueType.A, ByteForm.LITTLE); + writer.writeShort(player.getPosition().getLocalX(), ValueType.A); + for (int xCalc = (player.getPosition().getRegionX() - 6) / 8; xCalc <= ((player.getPosition().getRegionX() + 6) / 8); xCalc++) { + for (int yCalc = (player.getPosition().getRegionY() - 6) / 8; yCalc <= ((player.getPosition().getRegionY() + 6) / 8); yCalc++) { + int regionId = yCalc + (xCalc << 8); + if (forceSend || ((yCalc != 49) && (yCalc != 149) && (yCalc != 147) && (xCalc != 50) && ((xCalc != 49) || (yCalc != 47)))) { + int[] keys = LandscapeKeys.getKeys(regionId); + if (keys == null) { + player.teleport(new Position()); + keys = new int[4]; + for (int i = 0; i < keys.length; i++) { + keys[i] = 0; + } + } + for (int key : keys) { + writer.writeInt(key); + } + } + } + } + writer.writeByte(player.getPosition().getZ(), ValueType.C); + writer.writeShort(player.getPosition().getRegionY()); + player.getChannel().write(writer.getPacket()); + } + + /** + * Send items. + * + * @param interfaceId + * the interface id + * @param childId + * the child id + * @param type + * the type + * @param container + * the container + */ + public void sendItems(int interfaceId, int childId, int type, ItemContainer container) { + sendItems(interfaceId, childId, type, container.getItems()); + } + + /** + * Send items. + * + * @param interfaceId + * the interface id + * @param childId + * the child id + * @param type + * the type + * @param items + * the items + */ + public void sendItems(int interfaceId, int childId, int type, Item[] items) { + PacketWriter writer = new PacketWriter(new PacketHeader(255, LengthType.VARIABLE_SHORT)); + writer.writeShort(interfaceId); + writer.writeShort(childId); + writer.writeShort(type); + writer.writeShort(items.length); + for (int i = 0; i < items.length; i++) { + Item item = items[i]; + int id, amt; + if (item == null) { + id = -1; + amt = 0; + } else { + id = item.getId(); + amt = item.getAmount(); + } + if (amt > 254) { + writer.writeByte(255, ValueType.S); + writer.writeInt(amt, ByteForm.INVERSE_MIDDLE); + } else { + writer.writeByte(amt, ValueType.S); + } + writer.writeShort(id + 1, ByteForm.LITTLE); + } + player.getChannel().write(writer.getPacket()); + } + + /** + * Run script. + * + * @param id + * the id + * @param val + * the val + */ + public void runScript(int id, String val) { + PacketWriter writer = new PacketWriter(new PacketHeader(152, LengthType.VARIABLE_SHORT)); + writer.writeString(val); + writer.writeInt(id); + player.getChannel().write(writer.getPacket()); + } + + /** + * Run script. + * + * @param id + * the id + * @param o + * the o + * @param valstring + * the valstring + */ + public void runScript(int id, Object[] o, String valstring) { + if (valstring.length() != o.length) { + throw new IllegalArgumentException("Argument array size mismatch"); + } + + PacketWriter writer = new PacketWriter(new PacketHeader(152, LengthType.VARIABLE_SHORT)); + writer.writeString(valstring); + int j = 0; + for (int i = (valstring.length() - 1); i >= 0; i--) { + if (valstring.charAt(i) == 115) { + writer.writeString((String) o[j]); + } else { + writer.writeInt((Integer) o[j]); + } + j++; + } + writer.writeInt(id); + player.getChannel().write(writer.getPacket()); + } + + /** + * Sets the access mask. + * + * @param set + * the set + * @param window + * the window + * @param inter + * the inter + * @param off + * the off + * @param len + * the len + */ + public void setAccessMask(int set, int window, int inter, int off, int len) { + PacketWriter writer = new PacketWriter(new PacketHeader(223)); + writer.writeShort(len); + writer.writeShort(off, ValueType.A, ByteForm.LITTLE); + writer.writeShort(window, ByteForm.LITTLE); + writer.writeShort(inter, ByteForm.LITTLE); + writer.writeShort(set, ByteForm.LITTLE); + writer.writeShort(0, ByteForm.LITTLE); + player.getChannel().write(writer.getPacket()); + } + + /** + * Send string. + * + * @param string + * the string + * @param interfaceId + * the interface id + * @param childId + * the child id + */ + public void sendString(String string, int interfaceId, int childId) { + int sSize = string.length() + 5; + PacketWriter writer = new PacketWriter(new PacketHeader(179)); + writer.writeByte((byte) (sSize / 256)); + writer.writeByte((byte) (sSize % 256)); + writer.writeString(string); + writer.writeShort(childId); + writer.writeShort(interfaceId); + player.getChannel().write(writer.getPacket()); + } + + /** + * Send logout. + */ + public void sendLogout() { + player.getChannel().write(new PacketWriter(new PacketHeader(104)).getPacket()).addListener(new ChannelFutureListener() { + + @Override + public void operationComplete(ChannelFuture channelFuture) throws Exception { + channelFuture.getChannel().close(); + } + }); + } + + /** + * Send skill level. + * + * @param skill + * the skill + */ + public void sendSkillLevel(int skill) { + PacketWriter writer = new PacketWriter(new PacketHeader(217)); + writer.writeByte((int) Math.ceil(player.getSkills().currentLevel(skill)), ValueType.C); + writer.writeInt((int) player.getSkills().getExp(skill), ByteForm.INVERSE_MIDDLE); + writer.writeByte(skill, ValueType.C); + player.getChannel().write(writer.getPacket()); + } + + /** + * Send bonus. + * + * @param bonuses + * the bonuses + */ + public void sendBonus(int[] bonuses) { + int id = 35; + for (int i = 0; i < (bonuses.length - 1); i++) { + sendString((Settings.BONUSES[i] + ": " + (bonuses[i] > 0 ? "+" : "") + bonuses[i]), 667, id++); + if (id == 45) { + sendString(" ", 667, id++); + id = 47; + } + } + } + + /** + * Removes the side locking interface. + */ + public void removeSideLockingInterface() { + sendInterface(1, 548, 69, 56); + updateTabs(true); + sendCloseInterface(); + } + + /** + * Update tabs. + * + * @param shown + * the shown + */ + public void updateTabs(boolean shown) { + for (int b = 16; b <= 21; b++) { + sendInterfaceConfig(548, b, !shown); + } + + for (int a = 32; a <= 38; a++) { + sendInterfaceConfig(548, a, !shown); + } + + sendInterfaceConfig(548, 14, !shown); + sendInterfaceConfig(548, 31, !shown); + sendInterfaceConfig(548, 63, !shown); + + sendInterfaceConfig(548, 72, !shown); + } + + /** + * Open side locking interface. + * + * @param interfaceId + * the interface id + * @param inventoryId + * the inventory id + */ + public void openSideLockingInterface(int interfaceId, int inventoryId) { + sendInterface(interfaceId); + sendInventoryInterface(inventoryId); + } +} diff --git a/src/osiris/io/Packet.java b/src/osiris/io/Packet.java new file mode 100644 index 0000000..d59fbef --- /dev/null +++ b/src/osiris/io/Packet.java @@ -0,0 +1,134 @@ +package osiris.io; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.buffer.ChannelBuffers; + +/** + * An object wrapping a network packet. + * + * @author Blake + * + */ +public class Packet { + + /** + * The header. + */ + private PacketHeader header; + + /** + * The buffer. + */ + private ChannelBuffer buffer; + + /** + * Instantiates a new packet. + */ + public Packet() { + this(null, ChannelBuffers.dynamicBuffer()); + } + + /** + * Instantiates a new packet. + * + * @param header + * the header + */ + public Packet(PacketHeader header) { + this(header, ChannelBuffers.dynamicBuffer()); + } + + /** + * Instantiates a new packet. + * + * @param buffer + * the buffer + */ + public Packet(ChannelBuffer buffer) { + this(null, buffer); + } + + /** + * Instantiates a new packet. + * + * @param header + * the header + * @param buffer + * the buffer + */ + public Packet(PacketHeader header, ChannelBuffer buffer) { + this.header = header; + this.buffer = buffer; + } + + @Override + public String toString() { + return isRaw() ? "Packet[RAW]" : "Packet[opcode:" + header.getOpcode() + " length:" + header.getLength() + "]"; + } + + /** + * Checks if is raw. + * + * @return true, if is raw + */ + public boolean isRaw() { + return header == null; + } + + /** + * Sets the buffer. + * + * @param buffer + * the buffer to set + */ + public void setBuffer(ChannelBuffer buffer) { + this.buffer = buffer; + } + + /** + * Gets the buffer. + * + * @return the buffer + */ + public ChannelBuffer getBuffer() { + return buffer; + } + + /** + * Sets the header. + * + * @param header + * the header to set + */ + public void setHeader(PacketHeader header) { + this.header = header; + } + + /** + * Gets the header. + * + * @return the header + */ + public PacketHeader getHeader() { + return header; + } + +} diff --git a/src/osiris/io/PacketHeader.java b/src/osiris/io/PacketHeader.java new file mode 100644 index 0000000..9ca2de0 --- /dev/null +++ b/src/osiris/io/PacketHeader.java @@ -0,0 +1,170 @@ +package osiris.io; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * A header for a Packet. + * + * @author Blake + * + */ +public class PacketHeader { + + /** + * Various possible types of packet lengths. + */ + public enum LengthType { + /** + * A fixed length. + */ + FIXED, + /** + * A variable byte type length. + */ + VARIABLE_BYTE, + + /** + * A variable short type length. + */ + VARIABLE_SHORT + } + + /** + * The opcode. + */ + private int opcode; + + /** + * The length. + */ + private int length; + + /** + * The type. + */ + private LengthType lengthType; + + /** + * Instantiates a new packet header. + * + * @param opcode + * the opcode + */ + public PacketHeader(int opcode) { + this(opcode, -1, LengthType.FIXED); + } + + /** + * Instantiates a new packet header. + * + * @param opcode + * the opcode + * @param length + * the length + */ + public PacketHeader(int opcode, int length) { + this(opcode, length, LengthType.FIXED); + } + + /** + * Instantiates a new packet header. + * + * @param opcode + * the opcode + * @param type + * the type + */ + public PacketHeader(int opcode, LengthType type) { + this(opcode, -1, type); + } + + /** + * Instantiates a new packet header. + * + * @param opcode + * the opcode + * @param length + * the length + * @param type + * the type + */ + public PacketHeader(int opcode, int length, LengthType type) { + this.opcode = opcode; + this.length = length; + this.lengthType = type; + } + + /** + * Sets the opcode. + * + * @param opcode + * the opcode to set + */ + public void setOpcode(int opcode) { + this.opcode = opcode; + } + + /** + * Gets the opcode. + * + * @return the opcode + */ + public int getOpcode() { + return opcode; + } + + /** + * Sets the length. + * + * @param length + * the length to set + */ + public void setLength(int length) { + this.length = length; + } + + /** + * Gets the length. + * + * @return the length + */ + public int getLength() { + return length; + } + + /** + * Sets the length type. + * + * @param lengthType + * the type to set + */ + public void setLengthType(LengthType lengthType) { + this.lengthType = lengthType; + } + + /** + * Gets the type. + * + * @return the type + */ + public LengthType getLengthType() { + return lengthType; + } + +} diff --git a/src/osiris/io/PacketReader.java b/src/osiris/io/PacketReader.java new file mode 100644 index 0000000..079c957 --- /dev/null +++ b/src/osiris/io/PacketReader.java @@ -0,0 +1,438 @@ +package osiris.io; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * A utility class for reading data from Packets. + * + * @author Blake + * + */ +public class PacketReader { + + /** + * The packet. + */ + private Packet packet; + + /** + * Instantiates a new packet reader. + * + * @param packet + * the packet + */ + public PacketReader(Packet packet) { + this.setPacket(packet); + } + + /** + * Reads a signed byte. + * + * @return the int + */ + public int readByte() { + return readByte(true, ValueType.NORMAL); + } + + /** + * Read byte. + * + * @param signed + * the signed + * @return the int + */ + public int readByte(boolean signed) { + return readByte(signed, ValueType.NORMAL); + } + + /** + * Read byte. + * + * @param type + * the type + * @return the int + */ + public int readByte(ValueType type) { + return readByte(true, type); + } + + /** + * Read byte. + * + * @param signed + * the signed + * @param type + * the type + * @return the int + */ + public int readByte(boolean signed, ValueType type) { + int value = packet.getBuffer().readByte(); + switch (type) { + case A: + value = value - 128; + break; + case C: + value = -value; + break; + case S: + value = 128 - value; + break; + } + return signed ? value : value & 0xff; + } + + /** + * Read short. + * + * @return the int + */ + public int readShort() { + return readShort(true, ValueType.NORMAL, ByteForm.BIG); + } + + /** + * Read short. + * + * @param signed + * the signed + * @return the int + */ + public int readShort(boolean signed) { + return readShort(signed, ValueType.NORMAL, ByteForm.BIG); + } + + /** + * Read short. + * + * @param type + * the type + * @return the int + */ + public int readShort(ValueType type) { + return readShort(true, type, ByteForm.BIG); + } + + /** + * Read short. + * + * @param signed + * the signed + * @param type + * the type + * @return the int + */ + public int readShort(boolean signed, ValueType type) { + return readShort(signed, type, ByteForm.BIG); + } + + /** + * Read short. + * + * @param form + * the form + * @return the int + */ + public int readShort(ByteForm form) { + return readShort(true, ValueType.NORMAL, form); + } + + /** + * Read short. + * + * @param signed + * the signed + * @param form + * the form + * @return the int + */ + public int readShort(boolean signed, ByteForm form) { + return readShort(signed, ValueType.NORMAL, form); + } + + /** + * Read short. + * + * @param type + * the type + * @param form + * the form + * @return the int + */ + public int readShort(ValueType type, ByteForm form) { + return readShort(true, type, form); + } + + /** + * Read short. + * + * @param signed + * the signed + * @param type + * the type + * @param form + * the form + * @return the int + */ + public int readShort(boolean signed, ValueType type, ByteForm form) { + int value = 0; + switch (form) { + case BIG: + value |= readByte(false) << 8; + value |= readByte(false, type); + break; + case MIDDLE: + throw new UnsupportedOperationException("middle-endian short is impossible!"); + case INVERSE_MIDDLE: + throw new UnsupportedOperationException("inverse-middle-endian short is impossible!"); + case LITTLE: + value |= readByte(false, type); + value |= readByte(false) << 8; + break; + } + return signed ? value : value & 0xffff; + } + + /** + * Read int. + * + * @return the int + */ + public int readInt() { + return (int) readInt(true, ValueType.NORMAL, ByteForm.BIG); + } + + /** + * Read int. + * + * @param signed + * the signed + * @return the long + */ + public long readInt(boolean signed) { + return readInt(signed, ValueType.NORMAL, ByteForm.BIG); + } + + /** + * Read int. + * + * @param type + * the type + * @return the int + */ + public int readInt(ValueType type) { + return (int) readInt(true, type, ByteForm.BIG); + } + + /** + * Read int. + * + * @param signed + * the signed + * @param type + * the type + * @return the long + */ + public long readInt(boolean signed, ValueType type) { + return readInt(signed, type, ByteForm.BIG); + } + + /** + * Read int. + * + * @param form + * the form + * @return the int + */ + public int readInt(ByteForm form) { + return (int) readInt(true, ValueType.NORMAL, form); + } + + /** + * Read int. + * + * @param signed + * the signed + * @param form + * the form + * @return the long + */ + public long readInt(boolean signed, ByteForm form) { + return readInt(signed, ValueType.NORMAL, form); + } + + /** + * Read int. + * + * @param type + * the type + * @param form + * the form + * @return the int + */ + public int readInt(ValueType type, ByteForm form) { + return (int) readInt(true, type, form); + } + + /** + * Read int. + * + * @param signed + * the signed + * @param type + * the type + * @param form + * the form + * @return the long + */ + public long readInt(boolean signed, ValueType type, ByteForm form) { + long value = 0; + switch (form) { + case BIG: + value |= readByte(false) << 24; + value |= readByte(false) << 16; + value |= readByte(false) << 8; + value |= readByte(false, type); + break; + case MIDDLE: + value |= readByte(false) << 8; + value |= readByte(false, type); + value |= readByte(false) << 24; + value |= readByte(false) << 16; + break; + case INVERSE_MIDDLE: + value |= readByte(false) << 16; + value |= readByte(false) << 24; + value |= readByte(false, type); + value |= readByte(false) << 8; + break; + case LITTLE: + value |= readByte(false, type); + value |= readByte(false) << 8; + value |= readByte(false) << 16; + value |= readByte(false) << 24; + break; + } + return signed ? value : value & 0xffffffffL; + } + + /** + * Read long. + * + * @return the long + */ + public long readLong() { + return readLong(ValueType.NORMAL, ByteForm.BIG); + } + + /** + * Read long. + * + * @param type + * the type + * @return the long + */ + public long readLong(ValueType type) { + return readLong(type, ByteForm.BIG); + } + + /** + * Read long. + * + * @param form + * the form + * @return the long + */ + public long readLong(ByteForm form) { + return readLong(ValueType.NORMAL, form); + } + + /** + * Read long. + * + * @param type + * the type + * @param form + * the form + * @return the long + */ + public long readLong(ValueType type, ByteForm form) { + long value = 0; + switch (form) { + case BIG: + value |= (long) readByte(false) << 56L; + value |= (long) readByte(false) << 48L; + value |= (long) readByte(false) << 40L; + value |= (long) readByte(false) << 32L; + value |= (long) readByte(false) << 24L; + value |= (long) readByte(false) << 16L; + value |= (long) readByte(false) << 8L; + value |= readByte(false, type); + break; + case MIDDLE: + throw new UnsupportedOperationException("middle-endian long is not implemented!"); + case INVERSE_MIDDLE: + throw new UnsupportedOperationException("inverse-middle-endian long is not implemented!"); + case LITTLE: + value |= readByte(false, type); + value |= (long) readByte(false) << 8L; + value |= (long) readByte(false) << 16L; + value |= (long) readByte(false) << 24L; + value |= (long) readByte(false) << 32L; + value |= (long) readByte(false) << 40L; + value |= (long) readByte(false) << 48L; + value |= (long) readByte(false) << 56L; + break; + } + return value; + } + + /** + * Read string. + * + * @return the string + */ + public String readString() { + byte temp; + StringBuilder b = new StringBuilder(); + while ((temp = (byte) readByte()) != 0) { + b.append((char) temp); + } + return b.toString(); + } + + /** + * Sets the packet. + * + * @param packet + * the packet to set + */ + public void setPacket(Packet packet) { + this.packet = packet; + } + + /** + * Gets the packet. + * + * @return the packet + */ + public Packet getPacket() { + return packet; + } + +} \ No newline at end of file diff --git a/src/osiris/io/PacketWriter.java b/src/osiris/io/PacketWriter.java new file mode 100644 index 0000000..3d2f429 --- /dev/null +++ b/src/osiris/io/PacketWriter.java @@ -0,0 +1,497 @@ +package osiris.io; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.nio.ByteBuffer; + +import org.jboss.netty.buffer.ChannelBuffer; + +/** + * A utility class used to write data to Packets. + * + * @author Blake + * + */ +public class PacketWriter { + + /** + * Defines the current access type. + */ + public enum AccessType { + + /** + * Bit access. + */ + BIT_ACCESS, + + /** + * Byte access. + */ + BYTE_ACCESS + + } + + /** + * The access type. + */ + private AccessType accessType = AccessType.BYTE_ACCESS; + + /** + * The BI t_ mask. + */ + private static int BIT_MASK[] = { 0, 0x1, 0x3, 0x7, 0xf, 0x1f, 0x3f, 0x7f, 0xff, 0x1ff, 0x3ff, 0x7ff, 0xfff, 0x1fff, 0x3fff, 0x7fff, 0xffff, 0x1ffff, 0x3ffff, 0x7ffff, 0xfffff, 0x1fffff, 0x3fffff, 0x7fffff, 0xffffff, 0x1ffffff, 0x3ffffff, 0x7ffffff, 0xfffffff, 0x1fffffff, 0x3fffffff, 0x7fffffff, -1 }; + + /** + * The packet. + */ + private Packet packet; + + /** + * The current bit index. + */ + private int bitIndex; + + /** + * Instantiates a new packet writer. + */ + public PacketWriter() { + packet = new Packet(); + } + + /** + * Instantiates a new packet writer. + * + * @param header + * the header + */ + public PacketWriter(PacketHeader header) { + packet = new Packet(header); + } + + /** + * Sets the access type. + * + * @param type + * the new access type + */ + public void setAccessType(AccessType type) { + accessType = type; + if (accessType == AccessType.BIT_ACCESS) { + bitIndex = packet.getBuffer().writerIndex() * 8; + } else { + packet.getBuffer().writerIndex((bitIndex + 7) / 8); + } + } + + /** + * Check bit access. + */ + public void checkBitAccess() { + if (accessType != AccessType.BIT_ACCESS) { + throw new IllegalStateException("bit access must be enabled for bit writing"); + } + } + + /** + * Check byte access. + */ + public void checkByteAccess() { + if (accessType != AccessType.BYTE_ACCESS) { + throw new IllegalStateException("byte access must be enabled for byte writing"); + } + } + + /** + * Write bit. + * + * @param flag + * the flag + */ + public void writeBit(boolean flag) { + writeBit(flag ? 1 : 0); + } + + /** + * Write bit. + * + * @param value + * the value + */ + public void writeBit(int value) { + writeBits(1, value); + } + + int i = 0; + + /** + * Write bits. + * + * @param numBits + * the num bits + * @param value + * the value + */ + public void writeBits(int numBits, int value) { + if (numBits < 0 || numBits > 32) { + throw new IllegalArgumentException("Number of bits must be between 1 and 32 inclusive"); + } + + checkBitAccess(); + + ChannelBuffer buffer = packet.getBuffer(); + int bytePos = bitIndex >> 3; + int bitOffset = 8 - (bitIndex & 7); + bitIndex += numBits; + + int requiredSpace = bytePos - buffer.writerIndex() + 1; + requiredSpace += (numBits + 7) / 8; + + // Manual ensure-capacity. + if (buffer.writableBytes() < requiredSpace) { + int position = buffer.writerIndex(); + for (int i = 0; i < requiredSpace; i++) { + buffer.writeByte((byte) 0); + } + buffer.writerIndex(position); + } + + for (; numBits > bitOffset; bitOffset = 8) { + byte tmp = buffer.getByte(bytePos); + tmp &= ~BIT_MASK[bitOffset]; + tmp |= (value >> (numBits - bitOffset)) & BIT_MASK[bitOffset]; + buffer.setByte(bytePos++, tmp); + numBits -= bitOffset; + } + if (numBits == bitOffset) { + byte tmp = buffer.getByte(bytePos); + tmp &= ~BIT_MASK[bitOffset]; + tmp |= value & BIT_MASK[bitOffset]; + buffer.setByte(bytePos, tmp); + } else { + byte tmp = buffer.getByte(bytePos); + tmp &= ~(BIT_MASK[numBits] << (bitOffset - numBits)); + tmp |= (value & BIT_MASK[numBits]) << (bitOffset - numBits); + buffer.setByte(bytePos, tmp); + } + } + + /** + * Writes a byte. + * + * @param value + * the value + */ + public void writeByte(int value) { + writeByte(value, ValueType.NORMAL); + } + + /** + * Writes a byte. + * + * @param value + * the value + * @param type + * the type + */ + public void writeByte(int value, ValueType type) { + checkByteAccess(); + switch (type) { + case A: + value += 128; + break; + case C: + value = -value; + break; + case S: + value = 128 - value; + break; + } + packet.getBuffer().writeByte((byte) value); + } + + /** + * Write short. + * + * @param value + * the value + */ + public void writeShort(int value) { + writeShort(value, ValueType.NORMAL, ByteForm.BIG); + } + + /** + * Write short. + * + * @param value + * the value + * @param form + * the form + */ + public void writeShort(int value, ByteForm form) { + writeShort(value, ValueType.NORMAL, form); + } + + /** + * Write short. + * + * @param value + * the value + * @param type + * the type + */ + public void writeShort(int value, ValueType type) { + writeShort(value, type, ByteForm.BIG); + } + + /** + * Write short. + * + * @param value + * the value + * @param type + * the type + * @param form + * the form + */ + public void writeShort(int value, ValueType type, ByteForm form) { + switch (form) { + case BIG: + writeByte(value >> 8); + writeByte(value, type); + break; + case MIDDLE: + throw new UnsupportedOperationException("middle-endian short is impossible!"); + case INVERSE_MIDDLE: + throw new UnsupportedOperationException("inverse-middle-endian short is impossible!"); + case LITTLE: + writeByte(value, type); + writeByte(value >> 8); + break; + } + } + + /** + * Write int. + * + * @param value + * the value + */ + public void writeInt(int value) { + writeInt(value, ValueType.NORMAL, ByteForm.BIG); + } + + /** + * Write int. + * + * @param value + * the value + * @param type + * the type + */ + public void writeInt(int value, ValueType type) { + writeInt(value, type, ByteForm.BIG); + } + + /** + * Write int. + * + * @param value + * the value + * @param form + * the form + */ + public void writeInt(int value, ByteForm form) { + writeInt(value, ValueType.NORMAL, form); + } + + /** + * Write int. + * + * @param value + * the value + * @param type + * the type + * @param form + * the form + */ + public void writeInt(int value, ValueType type, ByteForm form) { + switch (form) { + case BIG: + writeByte(value >> 24); + writeByte(value >> 16); + writeByte(value >> 8); + writeByte(value, type); + break; + case MIDDLE: + writeByte(value >> 8); + writeByte(value, type); + writeByte(value >> 24); + writeByte(value >> 16); + break; + case INVERSE_MIDDLE: + writeByte(value >> 16); + writeByte(value >> 24); + writeByte(value, type); + writeByte(value >> 8); + break; + case LITTLE: + writeByte(value, type); + writeByte(value >> 8); + writeByte(value >> 16); + writeByte(value >> 24); + break; + } + } + + /** + * Write long. + * + * @param value + * the value + */ + public void writeLong(long value) { + writeLong(value, ValueType.NORMAL, ByteForm.BIG); + } + + /** + * Write long. + * + * @param value + * the value + * @param type + * the type + */ + public void writeLong(long value, ValueType type) { + writeLong(value, type, ByteForm.BIG); + } + + /** + * Write long. + * + * @param value + * the value + * @param form + * the form + */ + public void writeLong(long value, ByteForm form) { + writeLong(value, ValueType.NORMAL, form); + } + + /** + * Write long. + * + * @param value + * the value + * @param type + * the type + * @param form + * the form + */ + public void writeLong(long value, ValueType type, ByteForm form) { + switch (form) { + case BIG: + writeByte((int) (value >> 56)); + writeByte((int) (value >> 48)); + writeByte((int) (value >> 40)); + writeByte((int) (value >> 32)); + writeByte((int) (value >> 24)); + writeByte((int) (value >> 16)); + writeByte((int) (value >> 8)); + writeByte((int) value, type); + break; + case MIDDLE: + throw new UnsupportedOperationException("middle-endian long is not implemented!"); + case INVERSE_MIDDLE: + throw new UnsupportedOperationException("inverse-middle-endian long is not implemented!"); + case LITTLE: + writeByte((int) value, type); + writeByte((int) (value >> 8)); + writeByte((int) (value >> 16)); + writeByte((int) (value >> 24)); + writeByte((int) (value >> 32)); + writeByte((int) (value >> 40)); + writeByte((int) (value >> 48)); + writeByte((int) (value >> 56)); + break; + } + } + + /** + * Write string. + * + * @param msg + * the msg + */ + public void writeString(String msg) { + for (int i = 0; i < msg.length(); i++) { + writeByte(msg.getBytes()[i]); + } + writeByte(0); + } + + /** + * Writes a buffer. + * + * @param buffer + * the buffer + */ + public void writeBuffer(ChannelBuffer buffer) { + packet.getBuffer().writeBytes(buffer); + } + + /** + * Writes a buffer. + * + * @param buffer + * the buffer + */ + public void writeBuffer(ByteBuffer buffer) { + packet.getBuffer().writeBytes(buffer); + } + + /** + * Writes a buffer. + * + * @param buffer + * the buffer + */ + public void writeBytes(byte[] buffer) { + packet.getBuffer().writeBytes(buffer); + } + + /** + * Sets the packet. + * + * @param packet + * the packet to set + */ + public void setPacket(Packet packet) { + this.packet = packet; + } + + /** + * Gets the packet. + * + * @return the packet + */ + public Packet getPacket() { + return packet; + } + +} diff --git a/src/osiris/io/ProtocolConstants.java b/src/osiris/io/ProtocolConstants.java new file mode 100644 index 0000000..b32d5d1 --- /dev/null +++ b/src/osiris/io/ProtocolConstants.java @@ -0,0 +1,45 @@ +package osiris.io; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * @author Boomer + */ +public class ProtocolConstants { + + public static final int INTERFACE_CHATBOX = 752; + public static final int INTERFACE_HP_BUBBLE = 748; + public static final int INTERFACE_PRAYER_BUBBLE = 749; + public static final int INTERFACE_ENERGY_BUBBLE = 750; + public static final int INTERFACE_PLAYER_NAME = 137; + public static final int INTERFACE_CHAT_OPTIONS = 751; + public static final int INTERFACE_SKILL_TAB = 320; + public static final int INTERFACE_QUEST_TAB = 274; + public static final int INTERFACE_INVENTORY_TAB = 149; + public static final int INTERFACE_EQUIPMENT_TAB = 387; + public static final int INTERFACE_PRAYER_TAB = 271; + public static final int INTERFACE_FRIEND_TAB = 550; + public static final int INTERFACE_IGNORE_TAB = 551; + public static final int INTERFACE_CLAN_TAB = 589; + public static final int INTERFACE_SETTING_TAB = 261; + public static final int INTERFACE_EMOTE_TAB = 464; + public static final int INTERFACE_MUSIC_TAB = 187; + public static final int INTERFACE_LOGOUT_TAB = 182; + +} diff --git a/src/osiris/io/ValueType.java b/src/osiris/io/ValueType.java new file mode 100644 index 0000000..e0ad5b4 --- /dev/null +++ b/src/osiris/io/ValueType.java @@ -0,0 +1,49 @@ +package osiris.io; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * Represents one of the special value types. + * + * @author Blake + * + */ +public enum ValueType { + + /** + * Represents a normal data type. + */ + NORMAL, + + /** + * Represents a special-A data type. + */ + A, + + /** + * Represents a special-C data type. + */ + C, + + /** + * Represents a special-S data type. + */ + S + +} diff --git a/src/osiris/net/OsirisPipelineFactory.java b/src/osiris/net/OsirisPipelineFactory.java new file mode 100644 index 0000000..cfa184b --- /dev/null +++ b/src/osiris/net/OsirisPipelineFactory.java @@ -0,0 +1,51 @@ +package osiris.net; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.channel.ChannelPipelineFactory; +import org.jboss.netty.channel.Channels; + +import osiris.net.codec.OsirisPacketEncoder; +import osiris.net.codec.ServiceRequestDecoder; + +/** + * The default Osiris pipeline factory. + * + * @author Blake + * + */ +public class OsirisPipelineFactory implements ChannelPipelineFactory { + + /* + * (non-Javadoc) + * + * @see org.jboss.netty.channel.ChannelPipelineFactory#getPipeline() + */ + + @Override + public ChannelPipeline getPipeline() throws Exception { + ChannelPipeline pipeline = Channels.pipeline(); + pipeline.addLast("decoder", new ServiceRequestDecoder()); + pipeline.addLast("upHandler", new OsirisUpstreamHandler()); + pipeline.addLast("encoder", new OsirisPacketEncoder()); + return pipeline; + } + +} diff --git a/src/osiris/net/OsirisUpstreamHandler.java b/src/osiris/net/OsirisUpstreamHandler.java new file mode 100644 index 0000000..7819f0e --- /dev/null +++ b/src/osiris/net/OsirisUpstreamHandler.java @@ -0,0 +1,154 @@ +package osiris.net; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.util.HashMap; +import java.util.Map; + +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.channel.ChannelPipelineCoverage; +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 osiris.Main; +import osiris.ServerEngine; +import osiris.game.event.GameEvent; +import osiris.game.event.GameEventLookup; +import osiris.game.event.impl.ServiceRequestEvent; +import osiris.game.model.Player; +import osiris.io.Packet; + +/** + * The default Osiris upstream handler. + * + * @author Blake + * + */ +@ChannelPipelineCoverage("all") +public class OsirisUpstreamHandler extends SimpleChannelUpstreamHandler { + + /** + * The player map. + */ + private static Map playerMap = new HashMap(); + + /** + * Gets the player map. + * + * @return the player map + */ + public static Map getPlayerMap() { + return playerMap; + } + + /* + * (non-Javadoc) + * + * @see + * org.jboss.netty.channel.SimpleChannelUpstreamHandler#messageReceived( + * org.jboss.netty.channel.ChannelHandlerContext, + * org.jboss.netty.channel.MessageEvent) + */ + @Override + public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { + Packet packet = (Packet) e.getMessage(); + Class clazz = GameEventLookup.lookup(packet.getHeader().getOpcode()); + + // Standard game event. + if (clazz.getSuperclass().equals(GameEvent.class) && !clazz.equals(ServiceRequestEvent.class)) { + GameEvent event; + synchronized (playerMap) { + Player player = playerMap.get(e.getChannel().getId()); + event = clazz.getConstructor(Player.class, Packet.class).newInstance(player, packet); + } + ServerEngine.queueGameEvent(event); + } else { + // Service request. + GameEvent event = clazz.getConstructor(Channel.class, Packet.class).newInstance(e.getChannel(), packet); + event.process(); + event = null; + } + clazz = null; + } + + /* + * (non-Javadoc) + * + * @see + * org.jboss.netty.channel.SimpleChannelUpstreamHandler#channelOpen(org. + * jboss.netty.channel.ChannelHandlerContext, + * org.jboss.netty.channel.ChannelStateEvent) + */ + @Override + public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e) { + + } + + /* + * (non-Javadoc) + * + * @see + * org.jboss.netty.channel.SimpleChannelUpstreamHandler#channelClosed(org + * .jboss.netty.channel.ChannelHandlerContext, + * org.jboss.netty.channel.ChannelStateEvent) + */ + @Override + public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) { + Player player = playerMap.get(e.getChannel().getId()); + if (player != null) { + player.logout(); + } + } + + /* + * (non-Javadoc) + * + * @see + * org.jboss.netty.channel.SimpleChannelUpstreamHandler#channelDisconnected + * (org.jboss.netty.channel.ChannelHandlerContext, + * org.jboss.netty.channel.ChannelStateEvent) + */ + @Override + public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e) { + Player player = playerMap.get(e.getChannel().getId()); + if (player != null) { + player.logout(); + } + } + + /* + * (non-Javadoc) + * + * @see + * org.jboss.netty.channel.SimpleChannelUpstreamHandler#exceptionCaught( + * org.jboss.netty.channel.ChannelHandlerContext, + * org.jboss.netty.channel.ExceptionEvent) + */ + @Override + public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) { + if (Main.isLocal()) { + e.getCause().printStackTrace(); + } + e.getChannel().close(); + } + +} diff --git a/src/osiris/net/ProtocolException.java b/src/osiris/net/ProtocolException.java new file mode 100644 index 0000000..b0a2733 --- /dev/null +++ b/src/osiris/net/ProtocolException.java @@ -0,0 +1,44 @@ +package osiris.net; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * An exception raised by a protocol handler. + * + * @author Blake + * + */ +public class ProtocolException extends Exception { + + /** + * The Constant serialVersionUID. + */ + private static final long serialVersionUID = 1889655338734770422L; + + /** + * Instantiates a new protocol exception. + * + * @param msg + * the msg + */ + public ProtocolException(String msg) { + super(msg); + } + +} diff --git a/src/osiris/net/codec/LoginRequestDecoder.java b/src/osiris/net/codec/LoginRequestDecoder.java new file mode 100644 index 0000000..f18275e --- /dev/null +++ b/src/osiris/net/codec/LoginRequestDecoder.java @@ -0,0 +1,62 @@ +package osiris.net.codec; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +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.channel.ChannelPipelineCoverage; +import org.jboss.netty.handler.codec.frame.FrameDecoder; + +import osiris.io.Packet; +import osiris.io.PacketHeader; + +/** + * Decodes a login request. + * + * @author Blake + * + */ +@ChannelPipelineCoverage("all") +public class LoginRequestDecoder extends FrameDecoder { + + /* + * (non-Javadoc) + * + * @see + * org.jboss.netty.handler.codec.frame.FrameDecoder#decode(org.jboss.netty + * .channel.ChannelHandlerContext, org.jboss.netty.channel.Channel, + * org.jboss.netty.buffer.ChannelBuffer) + */ + + @Override + protected Object decode(ChannelHandlerContext ctx, Channel channel, ChannelBuffer in) throws Exception { + in.readByte(); // Skip session type + int length = in.readShort(); + if (in.readableBytes() < length) { + return null; + } + ChannelBuffer buffer = ChannelBuffers.buffer(length); + buffer.writeBytes(in); + Packet loginPacket = new Packet(new PacketHeader(258, length), buffer); + return loginPacket; + } + +} diff --git a/src/osiris/net/codec/OsirisDecoderState.java b/src/osiris/net/codec/OsirisDecoderState.java new file mode 100644 index 0000000..35e1b8c --- /dev/null +++ b/src/osiris/net/codec/OsirisDecoderState.java @@ -0,0 +1,44 @@ +package osiris.net.codec; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * Represents the different states of the Osiris packet decoder. + * + * @author Blake + * + */ +public enum OsirisDecoderState { + + /** + * Reading the opcode + */ + READ_OPCODE, + + /** + * Reading the length. + */ + READ_LENGTH, + + /** + * Reading the payload. + */ + READ_PAYLOAD + +} diff --git a/src/osiris/net/codec/OsirisPacketDecoder.java b/src/osiris/net/codec/OsirisPacketDecoder.java new file mode 100644 index 0000000..0120890 --- /dev/null +++ b/src/osiris/net/codec/OsirisPacketDecoder.java @@ -0,0 +1,388 @@ +package osiris.net.codec; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +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.channel.ChannelPipelineCoverage; +import org.jboss.netty.handler.codec.replay.ReplayingDecoder; + +import osiris.io.Packet; +import osiris.io.PacketHeader; + +/** + * The standard Osiris packet decoder. + * + * @author Blake + * + */ +@ChannelPipelineCoverage("all") +public class OsirisPacketDecoder extends ReplayingDecoder { + + /** + * The fixed packet lengths. + */ + public static final int[] PACKET_LENGTHS = new int[256]; + + static { + PACKET_LENGTHS[0] = -3; + PACKET_LENGTHS[1] = -3; + PACKET_LENGTHS[2] = 8; + PACKET_LENGTHS[3] = 8; // Item equipping. + PACKET_LENGTHS[4] = -3; + PACKET_LENGTHS[5] = -3; + PACKET_LENGTHS[6] = -3; + PACKET_LENGTHS[7] = 2; // First NPC option. + PACKET_LENGTHS[8] = -3; + PACKET_LENGTHS[9] = -3; + PACKET_LENGTHS[10] = -3; + PACKET_LENGTHS[11] = -3; + PACKET_LENGTHS[12] = -3; + PACKET_LENGTHS[13] = -3; + PACKET_LENGTHS[14] = -3; + PACKET_LENGTHS[15] = -3; + PACKET_LENGTHS[16] = -3; + PACKET_LENGTHS[17] = -3; + PACKET_LENGTHS[18] = -3; + PACKET_LENGTHS[19] = -3; + PACKET_LENGTHS[20] = -3; + PACKET_LENGTHS[21] = 6; // Buttons. + PACKET_LENGTHS[22] = 4; // Sent every time updateReq is set to true. + PACKET_LENGTHS[23] = -3; + PACKET_LENGTHS[24] = 8; + PACKET_LENGTHS[25] = -3; + PACKET_LENGTHS[26] = -3; + PACKET_LENGTHS[27] = -3; + PACKET_LENGTHS[28] = -3; + PACKET_LENGTHS[29] = -3; + PACKET_LENGTHS[30] = 8; + PACKET_LENGTHS[31] = -3; + PACKET_LENGTHS[32] = -3; + PACKET_LENGTHS[33] = -3; + PACKET_LENGTHS[34] = -3; + PACKET_LENGTHS[35] = -3; + PACKET_LENGTHS[36] = -3; + PACKET_LENGTHS[37] = 2; // Second player option. + PACKET_LENGTHS[38] = 2; // Item examine. + PACKET_LENGTHS[39] = -3; + PACKET_LENGTHS[40] = 16; + PACKET_LENGTHS[41] = -3; + PACKET_LENGTHS[42] = -3; + PACKET_LENGTHS[43] = 4; // value x + PACKET_LENGTHS[44] = -3; + PACKET_LENGTHS[45] = -3; + PACKET_LENGTHS[46] = -3; + PACKET_LENGTHS[47] = 0; // Idle packet. + PACKET_LENGTHS[48] = -3; + PACKET_LENGTHS[49] = -1; // Main walking packet. + PACKET_LENGTHS[50] = -3; + PACKET_LENGTHS[51] = -3; + PACKET_LENGTHS[52] = 2; // Second NPC option. + PACKET_LENGTHS[53] = -3; + PACKET_LENGTHS[54] = -3; + PACKET_LENGTHS[55] = -3; + PACKET_LENGTHS[56] = -3; + PACKET_LENGTHS[57] = -3; + PACKET_LENGTHS[58] = -3; + PACKET_LENGTHS[59] = 6; // Sent every time you click your mouse. + PACKET_LENGTHS[60] = 0; // New map region has been entered. + PACKET_LENGTHS[61] = 8; + PACKET_LENGTHS[62] = -3; + PACKET_LENGTHS[63] = 6; + PACKET_LENGTHS[64] = -3; + PACKET_LENGTHS[65] = -3; + PACKET_LENGTHS[66] = -3; + PACKET_LENGTHS[67] = -3; + PACKET_LENGTHS[68] = -3; + PACKET_LENGTHS[69] = -3; + PACKET_LENGTHS[70] = 8; // Magic on players. + PACKET_LENGTHS[71] = -3; + PACKET_LENGTHS[72] = -3; + PACKET_LENGTHS[73] = -3; + PACKET_LENGTHS[74] = -3; + PACKET_LENGTHS[75] = -3; + PACKET_LENGTHS[76] = -3; + PACKET_LENGTHS[77] = -3; + PACKET_LENGTHS[78] = -3; + PACKET_LENGTHS[79] = -3; + PACKET_LENGTHS[80] = -3; + PACKET_LENGTHS[81] = -3; + PACKET_LENGTHS[82] = -3; + PACKET_LENGTHS[83] = -3; + PACKET_LENGTHS[84] = 2; // Object examining. + PACKET_LENGTHS[85] = -3; + PACKET_LENGTHS[86] = -3; + PACKET_LENGTHS[87] = -3; + PACKET_LENGTHS[88] = 2; // NPC examining. + PACKET_LENGTHS[89] = -3; + PACKET_LENGTHS[90] = -3; + PACKET_LENGTHS[91] = -3; + PACKET_LENGTHS[92] = -3; + PACKET_LENGTHS[93] = -3; + PACKET_LENGTHS[94] = -3; + PACKET_LENGTHS[95] = -3; + PACKET_LENGTHS[96] = -3; + PACKET_LENGTHS[97] = -3; + PACKET_LENGTHS[98] = -3; + PACKET_LENGTHS[99] = 4; // Unknown. + PACKET_LENGTHS[100] = -3; + PACKET_LENGTHS[101] = -3; + PACKET_LENGTHS[102] = -3; + PACKET_LENGTHS[103] = -3; + PACKET_LENGTHS[104] = -3; + PACKET_LENGTHS[105] = -3; + PACKET_LENGTHS[106] = -3; + PACKET_LENGTHS[107] = -1; // Command packet. + PACKET_LENGTHS[108] = 0; // Interface closing. + PACKET_LENGTHS[109] = -3; + PACKET_LENGTHS[110] = -3; + PACKET_LENGTHS[111] = -3; + PACKET_LENGTHS[112] = -3; + PACKET_LENGTHS[113] = 4; // Interface buttons. + PACKET_LENGTHS[114] = -3; + PACKET_LENGTHS[115] = 0; // Ping packet, sends no bytes. + PACKET_LENGTHS[116] = -3; + PACKET_LENGTHS[117] = -1; // Sends a good few unknown bytes. + PACKET_LENGTHS[118] = -3; + PACKET_LENGTHS[119] = -1; // Minimap walking. + PACKET_LENGTHS[120] = -3; + PACKET_LENGTHS[121] = -3; + PACKET_LENGTHS[122] = -3; + PACKET_LENGTHS[123] = 2; // NPC attack option. + PACKET_LENGTHS[124] = -3; + PACKET_LENGTHS[125] = -3; + PACKET_LENGTHS[126] = -3; + PACKET_LENGTHS[127] = -3; + PACKET_LENGTHS[128] = -3; + PACKET_LENGTHS[129] = -3; + PACKET_LENGTHS[130] = -3; + PACKET_LENGTHS[131] = -3; + PACKET_LENGTHS[132] = 8; + PACKET_LENGTHS[133] = -3; + PACKET_LENGTHS[134] = -3; + PACKET_LENGTHS[135] = -3; + PACKET_LENGTHS[136] = -3; + PACKET_LENGTHS[137] = -3; + PACKET_LENGTHS[138] = -1; // Other walk clicking, such as items on + // ground. + PACKET_LENGTHS[139] = -3; + PACKET_LENGTHS[140] = -3; + PACKET_LENGTHS[141] = -3; + PACKET_LENGTHS[142] = -3; + PACKET_LENGTHS[143] = -3; + PACKET_LENGTHS[144] = -3; + PACKET_LENGTHS[145] = -3; + PACKET_LENGTHS[146] = -3; + PACKET_LENGTHS[147] = -3; + PACKET_LENGTHS[148] = -3; + PACKET_LENGTHS[149] = -3; + PACKET_LENGTHS[150] = -3; + PACKET_LENGTHS[151] = -3; + PACKET_LENGTHS[152] = -3; + PACKET_LENGTHS[153] = -3; + PACKET_LENGTHS[154] = -3; + PACKET_LENGTHS[155] = -3; + PACKET_LENGTHS[156] = -3; + PACKET_LENGTHS[157] = -3; + PACKET_LENGTHS[158] = 6; // First object option. + PACKET_LENGTHS[159] = -3; + PACKET_LENGTHS[160] = 2; // First player option. + PACKET_LENGTHS[161] = -3; + PACKET_LENGTHS[162] = -3; + PACKET_LENGTHS[163] = -3; + PACKET_LENGTHS[164] = -3; + PACKET_LENGTHS[165] = 4; // Settings buttons. + PACKET_LENGTHS[166] = -3; + PACKET_LENGTHS[167] = 9; // Switching items around in your inventory, + // banking, etc. + PACKET_LENGTHS[168] = -3; + PACKET_LENGTHS[169] = 6; // Buttons. + PACKET_LENGTHS[170] = -3; + PACKET_LENGTHS[171] = -3; + PACKET_LENGTHS[172] = -3; + PACKET_LENGTHS[173] = 6; + PACKET_LENGTHS[174] = -3; + PACKET_LENGTHS[175] = -3; + PACKET_LENGTHS[176] = -3; + PACKET_LENGTHS[177] = -3; + PACKET_LENGTHS[178] = -1; + PACKET_LENGTHS[179] = 12; // Item index switching. + PACKET_LENGTHS[180] = -3; + PACKET_LENGTHS[181] = -3; + PACKET_LENGTHS[182] = -3; + PACKET_LENGTHS[183] = -3; + PACKET_LENGTHS[184] = -3; + PACKET_LENGTHS[185] = -3; + PACKET_LENGTHS[186] = 8; // Item operate. + PACKET_LENGTHS[187] = -3; + PACKET_LENGTHS[188] = -3; + PACKET_LENGTHS[189] = -3; + PACKET_LENGTHS[190] = -3; + PACKET_LENGTHS[191] = -3; + PACKET_LENGTHS[192] = -3; + PACKET_LENGTHS[193] = -3; + PACKET_LENGTHS[194] = -3; + PACKET_LENGTHS[195] = -3; + PACKET_LENGTHS[196] = -3; + PACKET_LENGTHS[197] = -3; + PACKET_LENGTHS[198] = -3; + PACKET_LENGTHS[199] = 2; + PACKET_LENGTHS[200] = -3; + PACKET_LENGTHS[201] = 6; // Ground item picking up. + PACKET_LENGTHS[202] = -3; + PACKET_LENGTHS[203] = 8; // Item options 1. + PACKET_LENGTHS[204] = -3; + PACKET_LENGTHS[205] = -3; + PACKET_LENGTHS[206] = -3; + PACKET_LENGTHS[207] = -3; + PACKET_LENGTHS[208] = -3; + PACKET_LENGTHS[209] = -3; + PACKET_LENGTHS[210] = -3; + PACKET_LENGTHS[211] = 8; // Item dropping. + PACKET_LENGTHS[212] = 6; + PACKET_LENGTHS[213] = -3; + PACKET_LENGTHS[214] = 6; + PACKET_LENGTHS[215] = -3; + PACKET_LENGTHS[216] = -3; + PACKET_LENGTHS[217] = -3; + PACKET_LENGTHS[218] = -3; + PACKET_LENGTHS[219] = -3; + PACKET_LENGTHS[220] = 8; // Item eating, drinking, etc. + PACKET_LENGTHS[221] = -3; + PACKET_LENGTHS[222] = -1; // Public chat text. + PACKET_LENGTHS[223] = -3; + PACKET_LENGTHS[224] = 14; + PACKET_LENGTHS[225] = -3; + PACKET_LENGTHS[226] = -3; + PACKET_LENGTHS[227] = 2; // Third player option. + PACKET_LENGTHS[228] = 6; // Second object option. + PACKET_LENGTHS[229] = -3; + PACKET_LENGTHS[230] = -3; + PACKET_LENGTHS[231] = -3; + PACKET_LENGTHS[232] = 6; // Buttons. + PACKET_LENGTHS[233] = 6; // Buttons. + PACKET_LENGTHS[234] = -3; + PACKET_LENGTHS[235] = -3; + PACKET_LENGTHS[236] = -3; + PACKET_LENGTHS[237] = -3; + PACKET_LENGTHS[238] = -3; + PACKET_LENGTHS[239] = -3; + PACKET_LENGTHS[240] = -3; + PACKET_LENGTHS[241] = -3; + PACKET_LENGTHS[242] = -3; + PACKET_LENGTHS[243] = -3; + PACKET_LENGTHS[244] = -3; + PACKET_LENGTHS[245] = -3; + PACKET_LENGTHS[246] = -3; + PACKET_LENGTHS[247] = 4; // Unknown. + PACKET_LENGTHS[248] = 1; // Unknown. + PACKET_LENGTHS[249] = -3; + PACKET_LENGTHS[250] = 4; + PACKET_LENGTHS[251] = -3; + PACKET_LENGTHS[252] = -3; + PACKET_LENGTHS[253] = 2; + PACKET_LENGTHS[254] = -3; + PACKET_LENGTHS[255] = -3; + } + + /** The opcode. */ + private int opcode; + + /** The length. */ + private int length; + + /** The payload. */ + private ChannelBuffer payload; + + /** + * Instantiates a new osiris packet decoder. + */ + public OsirisPacketDecoder() { + super(OsirisDecoderState.READ_OPCODE); + } + + /* + * (non-Javadoc) + * + * @see + * org.jboss.netty.handler.codec.replay.ReplayingDecoder#decode(org.jboss + * .netty.channel.ChannelHandlerContext, org.jboss.netty.channel.Channel, + * org.jboss.netty.buffer.ChannelBuffer, java.lang.Enum) + */ + + @Override + protected Object decode(ChannelHandlerContext ctx, Channel channel, ChannelBuffer in, OsirisDecoderState state) throws Exception { + switch (state) { + case READ_OPCODE: + opcode = in.readByte() & 0xff; + checkpoint(OsirisDecoderState.READ_LENGTH); + case READ_LENGTH: + length = PACKET_LENGTHS[opcode]; + if (length == -1) { + length = in.readByte() & 0xff; + } + + // Unknown length. + if (length == -3) { + + /* + * Here we will attempt to salvage the stream by skipping all + * available bytes in hopes that this frame has not arrived + * fragmented and is contained completely within this one + * buffer. If this is the case, we can skip the entire frame and + * the next frame can be read properly. If not, the stream has + * been offset and the game will break. + * + * TODO: Possibly implement a more adaptive system? + */ + int skippedBytes = 0; + try { + // Attempt to drain all of the data out of the buffer. + for (; in.readable(); skippedBytes++) + in.readByte(); + } catch (Throwable throwable) { + /* + * This type of (replaying) buffer will throw a Throwable + * when it has no more available data. We will catch this + * Throwable here so that it doesn't fall back to the + * decoder and get handled by the replaying system. In + * effect, we are draining all available data out of this + * buffer (and from the stream) and hopefully skipping all + * of the data this unknown frame has. + */ + + // Now go and attempt to read the next frame. + checkpoint(OsirisDecoderState.READ_OPCODE); + break; + } + + } + checkpoint(OsirisDecoderState.READ_PAYLOAD); + case READ_PAYLOAD: + payload = ChannelBuffers.dynamicBuffer(); + payload.writeBytes(in.readBytes(length)); + checkpoint(OsirisDecoderState.READ_OPCODE); + return new Packet(new PacketHeader(opcode, length), payload); + } + return null; + } + +} \ No newline at end of file diff --git a/src/osiris/net/codec/OsirisPacketEncoder.java b/src/osiris/net/codec/OsirisPacketEncoder.java new file mode 100644 index 0000000..301f98e --- /dev/null +++ b/src/osiris/net/codec/OsirisPacketEncoder.java @@ -0,0 +1,82 @@ +package osiris.net.codec; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +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.channel.ChannelPipelineCoverage; +import org.jboss.netty.handler.codec.oneone.OneToOneEncoder; + +import osiris.io.Packet; +import osiris.io.PacketHeader.LengthType; + +/** + * The default Osiris packet encoder. + * + * @author Blake + * + */ +@ChannelPipelineCoverage("all") +public class OsirisPacketEncoder extends OneToOneEncoder { + + /* + * (non-Javadoc) + * + * @see + * org.jboss.netty.handler.codec.oneone.OneToOneEncoder#encode(org.jboss + * .netty.channel.ChannelHandlerContext, org.jboss.netty.channel.Channel, + * java.lang.Object) + */ + + @Override + protected Object encode(ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception { + // Write the payload. + Packet packet = (Packet) msg; + if (packet.isRaw()) { + ChannelBuffer out = ChannelBuffers.buffer(packet.getBuffer().writerIndex()); + out.writeBytes(packet.getBuffer()); + return out; + } + + // Write the full packet. + LengthType type = packet.getHeader().getLengthType(); + int headerLength = 1; + int payloadLength = packet.getBuffer().writerIndex(); + if (type == LengthType.VARIABLE_BYTE) { + headerLength += 1; + } else if (type == LengthType.VARIABLE_SHORT) { + headerLength += 2; + } + ChannelBuffer buffer = ChannelBuffers.buffer(headerLength + payloadLength); + buffer.writeByte((byte) packet.getHeader().getOpcode()); + if (type == LengthType.VARIABLE_BYTE) { + buffer.writeByte((byte) packet.getBuffer().writerIndex()); + } else if (type == LengthType.VARIABLE_SHORT) { + buffer.writeShort((short) packet.getBuffer().writerIndex()); + } + buffer.writeBytes(packet.getBuffer()); + + packet = null; + msg = null; + return buffer; + } + +} diff --git a/src/osiris/net/codec/ServiceRequestDecoder.java b/src/osiris/net/codec/ServiceRequestDecoder.java new file mode 100644 index 0000000..48c6f79 --- /dev/null +++ b/src/osiris/net/codec/ServiceRequestDecoder.java @@ -0,0 +1,60 @@ +package osiris.net.codec; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +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.channel.ChannelPipelineCoverage; +import org.jboss.netty.handler.codec.frame.FrameDecoder; + +import osiris.io.Packet; +import osiris.io.PacketHeader; + +/** + * Decodes a service request. + * + * @author Blake + * + */ +@ChannelPipelineCoverage("all") +public class ServiceRequestDecoder extends FrameDecoder { + + /* + * (non-Javadoc) + * + * @see + * org.jboss.netty.handler.codec.frame.FrameDecoder#decode(org.jboss.netty + * .channel.ChannelHandlerContext, org.jboss.netty.channel.Channel, + * org.jboss.netty.buffer.ChannelBuffer) + */ + + @Override + protected Object decode(ChannelHandlerContext ctx, Channel channel, ChannelBuffer in) throws Exception { + if (in.readableBytes() > 0) { + ChannelBuffer buffer = ChannelBuffers.buffer(in.readableBytes()); + buffer.writeBytes(in); + Packet packet = new Packet(new PacketHeader(256, buffer.writerIndex()), buffer); + return packet; + } + return null; + } + +} diff --git a/src/osiris/net/codec/UpdateRequestDecoder.java b/src/osiris/net/codec/UpdateRequestDecoder.java new file mode 100644 index 0000000..1459f43 --- /dev/null +++ b/src/osiris/net/codec/UpdateRequestDecoder.java @@ -0,0 +1,59 @@ +package osiris.net.codec; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +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.channel.ChannelPipelineCoverage; +import org.jboss.netty.handler.codec.frame.FrameDecoder; + +import osiris.io.Packet; +import osiris.io.PacketHeader; + +/** + * A decoder for an update request. + * + * @author Blake + * + */ +@ChannelPipelineCoverage("all") +public class UpdateRequestDecoder extends FrameDecoder { + + /* + * (non-Javadoc) + * + * @see + * org.jboss.netty.handler.codec.frame.FrameDecoder#decode(org.jboss.netty + * .channel.ChannelHandlerContext, org.jboss.netty.channel.Channel, + * org.jboss.netty.buffer.ChannelBuffer) + */ + + @Override + protected Object decode(ChannelHandlerContext ctx, Channel channel, ChannelBuffer in) throws Exception { + if (in.readableBytes() % 4 == 0) { + ChannelBuffer buffer = ChannelBuffers.buffer(in.readableBytes()); + buffer.writeBytes(in); + Packet packet = new Packet(new PacketHeader(257, buffer.writerIndex()), buffer); + return packet; + } + return null; + } +} diff --git a/src/osiris/util/AsyncTask.java b/src/osiris/util/AsyncTask.java new file mode 100644 index 0000000..9af89ac --- /dev/null +++ b/src/osiris/util/AsyncTask.java @@ -0,0 +1,82 @@ +package osiris.util; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.util.concurrent.Callable; +import java.util.concurrent.FutureTask; + +/** + * An asynchronous task object that calls a method upon completion instead of + * blocking and waiting for the task to finish. + * + * @author Blake + * @param + * the value type + * + */ +public class AsyncTask extends FutureTask { + + /** + * The Interface CompletionReceiver. + * + * @param + * the value type + */ + public static interface CompletionReceiver { + + /** + * On complete. + * + * @param task + * the task + */ + public void onComplete(AsyncTask task); + + } + + /** + * The recv. + */ + private final CompletionReceiver recv; + + /** + * Instantiates a new async task. + * + * @param task + * the task + * @param recv + * the completion receiver + */ + public AsyncTask(Callable task, CompletionReceiver recv) { + super(task); + this.recv = recv; + } + + /* + * (non-Javadoc) + * + * @see java.util.concurrent.FutureTask#done() + */ + + @Override + public void done() { + recv.onComplete(this); + } + +} diff --git a/src/osiris/util/BankUtilities.java b/src/osiris/util/BankUtilities.java new file mode 100644 index 0000000..8c2b413 --- /dev/null +++ b/src/osiris/util/BankUtilities.java @@ -0,0 +1,74 @@ +package osiris.util; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * The Class BankUtilities. + * + * @author Boomer + * + */ +public class BankUtilities { + + /** + * The Constant BANK_SIZE. + */ + public static final int BANK_SIZE = 500; + + /** + * Tab id to container slot. + * + * @param tabId + * the tab id + * @return the int + */ + public static int tabIdToContainerSlot(int tabId) { + switch (tabId) { + case 39: + case 52: + return 0; + case 37: + case 53: + return 1; + case 35: + case 54: + return 2; + case 33: + case 55: + return 3; + case 31: + case 56: + return 4; + case 29: + case 57: + return 5; + case 27: + case 58: + return 6; + case 25: + case 59: + return 7; + case 41: + case 51: + return -1; + } + return -1; + } + +} diff --git a/src/osiris/util/ConsoleProxy.java b/src/osiris/util/ConsoleProxy.java new file mode 100644 index 0000000..06b9d22 --- /dev/null +++ b/src/osiris/util/ConsoleProxy.java @@ -0,0 +1,72 @@ +package osiris.util; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.io.OutputStream; +import java.io.PrintStream; +import java.text.SimpleDateFormat; +import java.util.Date; + +// TODO: Auto-generated Javadoc +/** + * The Class ConsoleProxy. + * + * @author Blake + */ +public class ConsoleProxy extends PrintStream { + + /** + * Instantiates a new console proxy. + * + * @param out + * the out + */ + public ConsoleProxy(OutputStream out) { + super(out); + } + + /* + * (non-Javadoc) + * + * @see java.io.PrintStream#print(java.lang.String) + */ + @Override + public void print(String msg) { + msg = format(msg); + super.print(msg); + } + + /** + * discombobulated corpse termination urbanization governmentalibation + * explosive montezuma's blasting bowel revenge! + * + * yeah, that sounded good + * + * @param msg + * the msg + * @return the string + */ + private String format(String msg) { + return "[" + sdf.format(new Date()) + "]: " + msg; + } + + /** The sdf. */ + private SimpleDateFormat sdf = new SimpleDateFormat(); + +} diff --git a/src/osiris/util/DistancedTask.java b/src/osiris/util/DistancedTask.java new file mode 100644 index 0000000..a5ed8d4 --- /dev/null +++ b/src/osiris/util/DistancedTask.java @@ -0,0 +1,147 @@ +package osiris.util; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import static osiris.util.Settings.DISTANCED_TASK_CHECK_DELAY; + +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import osiris.ServerEngine; +import osiris.game.model.Character; +import osiris.game.model.Position; + +/** + * A task that fires when the position of a Character comes within a specified + * distance of another position. + * + * @author Blake + * + */ +public abstract class DistancedTask implements Runnable { + + /** + * The character. + */ + private final Character character; + + /** + * The position. + */ + private final Position position; + + /** + * The distance. + */ + private final int distance; + + /** + * The task. + */ + private ScheduledFuture task; + + /** + * Instantiates a new distanced task. + * + * @param character + * the character + * @param position + * the position + * @param distance + * the distance + */ + public DistancedTask(Character character, Position position, int distance) { + this.character = character; + this.position = position; + this.distance = distance; + } + + /** + * Executes the task. + */ + public abstract void execute(); + + /* + * (non-Javadoc) + * + * @see java.lang.Runnable#cycle() + */ + @Override + public void run() { + /* + * We must schedule a new Runnable and hold a reference to the returned + * ScheduledFuture so that the task itself can cancelAttack the + * repeating execution (used for checking) of itself once the condition + * is met and the actual logic within execute() runs. + */ + task = ServerEngine.getScheduler().scheduleAtFixedRate(new Runnable() { + @Override + public void run() { + if (atDestination()) { + execute(); + task.cancel(false); + } + } + }, 0, DISTANCED_TASK_CHECK_DELAY, TimeUnit.MILLISECONDS); + } + + /** + * At destination. + * + * @return true, if successful + */ + public boolean atDestination() { + return Utilities.getDistance(this.getPosition(), character.getPosition()) <= distance; + } + + /** + * Cancel. + */ + public void cancel() { + task.cancel(false); + } + + /** + * Gets the character. + * + * @return the character + */ + public Character getCharacter() { + return character; + } + + /** + * Gets the position. + * + * @return the position + */ + public Position getPosition() { + return position; + } + + /** + * Gets the distance. + * + * @return the distance + */ + public int getDistance() { + return distance; + } + +} diff --git a/src/osiris/util/LandscapeKeys.java b/src/osiris/util/LandscapeKeys.java new file mode 100644 index 0000000..88c76c7 --- /dev/null +++ b/src/osiris/util/LandscapeKeys.java @@ -0,0 +1,93 @@ +package osiris.util; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.io.BufferedWriter; +import java.io.DataInputStream; +import java.io.FileInputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +// TODO: Auto-generated Javadoc +/** + * Keys for landscape unpacking. + * + * @author Blake + * + */ +public class LandscapeKeys { + + /** The Constant keyMap. */ + private static final Map keyMap = new HashMap(); + + /** + * Load keys. + * + * @param fileName + * the file name + * @throws IOException + * Signals that an I/O exception has occurred. + */ + public static void loadKeys(String fileName) throws IOException { + DataInputStream in = new DataInputStream(new FileInputStream(fileName)); + while (in.available() > 20) { + int regionId = in.readInt(); + int[] regionKeys = new int[4]; + for (int i = 0; i < regionKeys.length; i++) { + regionKeys[i] = in.readInt(); + } + keyMap.put(regionId, regionKeys); + } + } + + /** + * Gets the keys. + * + * @param regionId + * the region id + * @return the keys + */ + public static int[] getKeys(int regionId) { + return keyMap.get(regionId); + } + + /** + * Dump. + */ + public static void dump() { + for (Map.Entry entry : keyMap.entrySet()) { + int regionId = entry.getKey(); + int[] keys = entry.getValue(); + try { + BufferedWriter writer = new BufferedWriter(new FileWriter("./mapdata/" + regionId + ".txt")); + for (int key : keys) { + writer.write(key + ""); + writer.newLine(); + } + writer.flush(); + writer.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + +} diff --git a/src/osiris/util/RubyInterpreter.java b/src/osiris/util/RubyInterpreter.java new file mode 100644 index 0000000..2679c68 --- /dev/null +++ b/src/osiris/util/RubyInterpreter.java @@ -0,0 +1,111 @@ +package osiris.util; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; + +import javax.script.Invocable; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; + +import osiris.Main; + +/** + * The Class RubyInterpreter. + * + * @author Blakeman8192 + */ +public class RubyInterpreter { + + /** The Constant engine. */ + private static final ScriptEngine engine; + + static { + if (Main.isLocal()) { + System.out.println("Loading JRuby script engine..."); + } + ScriptEngineManager manager = new ScriptEngineManager(); + engine = manager.getEngineByName("jruby"); + if (engine == null) { + throw new RuntimeException("JRuby engine not found!"); + } + } + + /** + * Invoke. + * + * @param identifier + * the identifier + * @param args + * the args + */ + public static void invoke(String identifier, Object... args) { + Invocable i = (Invocable) engine; + try { + i.invokeFunction(identifier, args); + } catch (ScriptException ex) { + ex.printStackTrace(); + } catch (NoSuchMethodException ex) { + ex.printStackTrace(); + } + } + + /** + * Gets the variable. + * + * @param key + * the key + * @return the variable + */ + public static Object getVariable(String key) { + return engine.get(key); + } + + /** + * Load scripts. + * + * @param directory + * the directory + * @throws IOException + * Signals that an I/O exception has occurred. + * @throws ScriptException + * the script exception + */ + public static void loadScripts(File directory) throws IOException, ScriptException { + if (!directory.exists() || !directory.isDirectory()) { + throw new IllegalArgumentException("Invalid scripts directory!"); + } + for (File file : directory.listFiles()) { + if (file.isDirectory()) { + loadScripts(file); + } else { + if (file.getName().endsWith(".rb")) { + if (Main.isLocal()) { + System.out.println("\tScript: " + file.getName()); + } + engine.eval(new FileReader(file)); + } + } + } + } + +} diff --git a/src/osiris/util/Settings.java b/src/osiris/util/Settings.java new file mode 100644 index 0000000..5ecb2ce --- /dev/null +++ b/src/osiris/util/Settings.java @@ -0,0 +1,422 @@ +package osiris.util; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.awt.Dimension; + +import osiris.data.SettingsLoader; + +/** + * Server settings. + * + * @author Blake + */ +public final class Settings { + + static { + PORT = SettingsLoader.getValueAsInt("server_port"); + SERVER_NAME = SettingsLoader.getValue("server_name"); + EXP_RATE = Double.parseDouble(SettingsLoader.getValue("exp_rate")); + SQLITE_SAVING = SettingsLoader.getValueAsBoolean("sqlite_saving"); + + SERVER_HOST = SettingsLoader.getValue("server_host"); + SERVER_DATABASE = SettingsLoader.getValue("server_database"); + SERVER_USERNAME = SettingsLoader.getValue("server_username"); + SERVER_PASSWORD = SettingsLoader.getValue("server_password"); + + DEFAULT_X = SettingsLoader.getValueAsInt("start_x"); + DEFAULT_Y = SettingsLoader.getValueAsInt("start_y"); + DEFAULT_Z = SettingsLoader.getValueAsInt("start_z"); + } + + /** The Constant SERVER_NAME. */ + public static final String SERVER_NAME; + + /** The Constant TIMEOUT_DELAY. */ + public static final int TIMEOUT_DELAY = 10; + + /** The Constant SCRIPTS_DIRECTORY. */ + public static final String SCRIPTS_DIRECTORY = "./src/ruby/"; + + /** The Constant FRIENDS_LIMIT. */ + public static final int FRIENDS_LIMIT = 200; + + /** The Constant IGNORE_LIMIT. */ + public static final int IGNORE_LIMIT = 100; + + /** The Constant MAX_SPECIAL_ENERGY. */ + public static final int MAX_SPECIAL_ENERGY = 1000; + + /** Sql Info for server database. */ + public static final String SERVER_HOST, SERVER_DATABASE, SERVER_USERNAME, SERVER_PASSWORD; + + /** The SQLIT e_ saving. */ + public static boolean SQLITE_SAVING; + + /** + * The Constant EXP_RATE. + */ + public static final double EXP_RATE; + + /** The Constant SPELL_STAFFS. */ + public static final int[] SPELL_STAFFS = { 1409, 4170, 2415, 2416, 2417 }; + + /** The Constant STAFF_ZAMORAK. */ + public static final int STAFF_IBAN = 0, STAFF_SLAYER = 1, STAFF_SARADOMIN = 2, STAFF_GUTHIX = 3, STAFF_ZAMORAK = 4; + + /** + * The Constant TICKRATE. + */ + public static final int TICKRATE = 600; + + /** + * The Constant DISTANCED_TASK_CHECK_DELAY. + */ + public static final int DISTANCED_TASK_CHECK_DELAY = TICKRATE * 2; + + /** + * The Constant MAX_PLAYERS. + */ + public static final int MAX_PLAYERS = 2000; + + /** + * The Constant MAX_NPCS. + */ + public static final int MAX_NPCS = 3200; + + /** + * The Constant DEFAULT_X. + */ + public static final int DEFAULT_X; + + /** + * The Constant DEFAULT_Y. + */ + public static final int DEFAULT_Y; + + /** + * The Constant DEFAULT_Z. + */ + public static final int DEFAULT_Z; + + /** + * The Constant PORT. + */ + public static final int PORT; + + /** + * The Constant LANDSCAPE_KEYS_FILE. + */ + public static final String LANDSCAPE_KEYS_FILE = "./data/landscapekeys.bin"; + + /** + * The Constant NPC_SPAWNS. + */ + public static final String NPC_SPAWNS = "npc-spawns.xml"; + + /** The Constant NPC_DROPS. */ + public static final String NPC_DROPS = "npc-drops.xml"; + + /** The constant GROUND_ITEMS. */ + public static final String GROUND_ITEMS = "ground-items.xml"; + + /** The Constant DIALOGUES. */ + public static final String DIALOGUES = "dialogues.xml"; + + /** The Constant WORLD_OBJECTS. */ + public static final String WORLD_OBJECTS = "world-objects.xml"; + + /** The Constant POSITION_CHANGE. */ + public static final String POSITION_CHANGE = "position-change.xml"; + + /** + * Single square dimension. + */ + public static final Dimension SINGULAR_DIMENSION = new Dimension(1, 1); + + /** + * The Constant NPC_MOVEMENT_TIME. + */ + public static final int NPC_MOVEMENT_TIME = 3; + + /** + * The Equipment slots. + */ + public static final int SLOT_HAT = 0; + + /** + * The Constant SLOT_CAPE. + */ + public static final int SLOT_CAPE = 1; + + /** + * The Constant SLOT_AMULET. + */ + public static final int SLOT_AMULET = 2; + + /** + * The Constant SLOT_WEAPON. + */ + public static final int SLOT_WEAPON = 3; + + /** + * The Constant SLOT_CHEST. + */ + public static final int SLOT_CHEST = 4; + + /** + * The Constant SLOT_SHIELD. + */ + public static final int SLOT_SHIELD = 5; + + /** + * The Constant SLOT_LEGS. + */ + public static final int SLOT_LEGS = 7; + + /** + * The Constant SLOT_HANDS. + */ + public static final int SLOT_HANDS = 9; + + /** + * The Constant SLOT_FEET. + */ + public static final int SLOT_FEET = 10; + + /** + * The Constant SLOT_RING. + */ + public static final int SLOT_RING = 12; + + /** The Constant SLOT_ARROWS. */ + public static final int SLOT_ARROWS = 13; + + /** + * The Constant CAPES. + */ + public static final String[] CAPES = { "cape", "Cape", "Ranging cape", "Hunter cape", "Ava's", "cloak" }; + + /** + * The Constant 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", "Runecrafter hat", "Slayer helmet", "Beret mask", "Cavalier mask", "beret", "Lunar helm", "tiara", "Customs hat", "Rogue mask", "Bunny ears", "Earmuffs", "Proselyte sallet", "goggles", "headgear", "nose", "Desert disguise", "mitre", "Feather headdress", "tricorn hat", "A powdered wig", "Verac's helm", "Saradomin full helm", "Runecrafter hat", "coif 100", "Skeleton mask", "Chicken head", "Sleeping cap" }; + + /** + * The Constant BOOTS. + */ + public static final String[] BOOTS = { "boots", "Boots", "Chicken feet", "Flippers" }; + + /** + * The Constant GLOVES. + */ + public static final String[] GLOVES = { "hook", "gloves", "gauntlets", "Gloves", "vambraces", "vamb", "bracers", "Runecrafter gloves", "bracelet", "brace", "bracelet(4)", "bracelet(3)", "bracelet(2)", "bracelet(1)", "brace(4)", "brace(3)", "brace(2)", "brace(1)" }; + + /** + * The Constant SHIELDS. + */ + public static final String[] SHIELDS = { "spikeshield", "berserker shield", "kiteshield", "sq shield", "Toktz-ket", "books", "book", "kiteshield (t)", "kiteshield (g)", "kiteshield(h)", "defender", "satchel", "Book", "Saradomin kite", "shield" }; + + /** + * The Constant AMULETS. + */ + public static final String[] AMULETS = { "amulet", "necklace", "Amulet of", "Strength amulet(t)", "stole", "Gnome scarf", "Armadyl pendant" }; + + /** + * The Constant ARROWS. + */ + public static final String[] ARROWS = { "arrow", "arrows", "arrow(p)", "arrow(p+)", "arrow(p++)", "bolts", "bolt", "Bolt rack", "Opal bolts", "Dragon bolts", "bolts", "Dragon arrow" }; + + /** + * The Constant RINGS. + */ + public static final String[] RINGS = { "ring", "Ring", "Ring of stone" }; + + /** + * The Constant BODY. + */ + public static final String[] BODY = { "tunic", "platebody", "chainbody", "robe top", "Wizard robe", "robetop", "leathertop", "platemail", "top", "brassard", "Robe top", "body", "platebody (t)", "platebody (g)", "body(g)", "body_(g)", "chestplate", "torso", "Woven top", "shirt", "Runecrafter robe", "Saradomin d'hide", "Zamorak d'hide", "Guthix dragonhide", "Saradomin plate", "Princess blouse", "Doctors' gown", "Varrock armour", "Proselyte hauberk", "Zombie shirt", "Moonclan armour", "Chicken wings", "blouse" }; + + /** + * The Constant LEGS. + */ + public static final String[] LEGS = { "leggings", "platelegs", "plateskirt", "shorts", "skirt", "bottoms", "chaps", "platelegs (t)", "platelegs (g)", "bottom", "skirt", "skirt (g)", "skirt (t)", "chaps (g)", "chaps (t)", "tassets", "legs", "Runecrafter skirt", "Void knight robe", "Saradomin d'hide", "Zamorak d'hide", "Guthix dragonhide", "Princess skirt", "navy slacks", "trousers", "Proselyte cuisse", "Proselyte tasset", "3rd age robe", "Skeleton leggings", "Pantaloons", "skirt", "Skirt" }; + + /** + * The Constant 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", "Ivandis flail", "talisman staff", "Gnomecopter", "Toy kite", "crystal bow", "anchor", "Seercull", "cane", "Gadderhammer", "Scythe", "sceptre", "Banner", "spear", "crozier", "pole", "Keris", "hasta", "Excalibur", "hasta(p++)", "hasta(p+)", "hasta(kp)", "hasta(p)", "Fixed device", "x-bow", + "Drag dagger", "Torag's hammers", "Rubber chicken", "banner", "Wand", "wand", "Snowball", "zanik", "Basket of eggs", "Training bow", "mjolnir", "claws", "warhammer" }; + /* Fullbody is an item that covers your arms. */ + /** + * The Constant FULL_BODY. + */ + public static final String[] FULL_BODY = { "robe", "blouse", "tunic", "top", "shirt", "Ahrims robetop", "Karils leathertop", "brassard", "platebody (t)", "platebody (g)", "chestplate", "torso", "Runecrafter robe", "Saradomin d'hide", "Zamorak d'hide", "Guthix dragonhide", "Dragon chainbody", "Doctors' gown", "Varrock armour", "Proselyte hauberk", "Proselyte hauberk", "Robe top", "robetop", "Saradomin plate", "Runecrafter skirt", "Runecrafter robe", "platebody", "Moonclan armour", "Decorative armour", "Chicken wings" }; + /* Fullhat covers your head but not your beard. */ + /** + * The Constant FULL_HAT. + */ + public static final String[] FULL_HAT = { "med helm", "coif", "hood", "Initiate helm", "Coif", "Helm of neitiznot", "beret", "Customs hat", "Proselyte sallet", "headgear", "Desert disguise", "Feather headdress", "tricorn hat", "Reindeer hat", "Armadyl helmet", "A powdered wig", "Void melee helm", "Void ranger helm", "Void mage helm", "hood 100", "coif 100", "mitre", "Chicken head", "Sleeping cap", "robin hood hat" }; + /* Fullmask covers your entire head. */ + /** + * The Constant FULL_MASK. + */ + public static final String[] FULL_MASK = { "full helm", "mask", "full helm (t)", "full helm (g)", "Lunar helm", "Rogue mask", "Slayer helmet", "heraldic helm", "Grim reaper hood", "Saradomin full helm", "Dharok's helm", "Guthan's helm", "Torag's helm", "Verac's helm" }; + + /** + * The Stat bonuses. + */ + public static final String[] BONUSES = { "Stab", "Slash", "Crush", "Magic", "Range", "Stab", "Slash", "Crush", "Magic", "Range", "Strength", "Prayer" }; + + /** + * The Constant EMOTES. + */ + public static final int[] EMOTES = { 855, 856, 858, 859, 857, 863, 2113, 862, 864, 861, 2109, 2111, 866, 2106, 2107, 2108, 860, 0x558, 2105, 2110, 865, 2112, 0x84F, 0x850, 1131, 1130, 1129, 1128, 4275, 10017, 4280, 4276, 3544, 3543, 7272, 2836, 6111, -1, 7531, 2414, 8770, 9990 }; + + /** + * The Constant EMOTE_GRAPHICS. + */ + public static final int[][] EMOTE_GRAPHICS = { { 19, 574 }, { 33, 712 }, { 36, 1244 }, { 41, 1537 }, { 42, 1553 }, { 43, 1734 } }; + + /** + * The Constant EMOTE_EXPLORE. + */ + public static final int EMOTE_GOBLIN_BOW = 0; + + /** + * The Constant EMOTE_GOBLIN_SALUTE. + */ + public static final int EMOTE_GOBLIN_SALUTE = 1; + + /** + * The Constant EMOTE_GLASS_BOX. + */ + public static final int EMOTE_GLASS_BOX = 2; + + /** + * The Constant EMOTE_CLIMB_ROPE. + */ + public static final int EMOTE_CLIMB_ROPE = 3; + + /** + * The Constant EMOTE_LEAN. + */ + public static final int EMOTE_LEAN = 4; + + /** + * The Constant EMOTE_GLASS_WALL. + */ + public static final int EMOTE_GLASS_WALL = 5; + + /** + * The Constant EMOTE_IDEA. + */ + public static final int EMOTE_IDEA = 9; + + /** + * The Constant EMOTE_STOMP. + */ + public static final int EMOTE_STOMP = 7; + + /** + * The Constant EMOTE_FLAP. + */ + public static final int EMOTE_FLAP = 8; + + /** + * The Constant EMOTE_SLAP_HEAD. + */ + public static final int EMOTE_SLAP_HEAD = 6; + + /** + * The Constant EMOTE_ZOMBIE_WALK. + */ + public static final int EMOTE_ZOMBIE_WALK = 1; + + /** + * The Constant EMOTE_ZOMBIE_DANCE. + */ + public static final int EMOTE_ZOMBIE_DANCE = 11; + + /** + * The Constant EMOTE_ZOMBIE_HAND. + */ + public static final int EMOTE_ZOMBIE_HAND = 12; + + /** + * The Constant EMOTE_SCARED. + */ + public static final int EMOTE_SCARED = 13; + + /** + * The Constant EMOTE_BUNNY_HOP. + */ + public static final int EMOTE_BUNNY_HOP = 14; + + /** + * The Constant EMOTE_SKILL_CAPE. + */ + public static final int EMOTE_SKILL_CAPE = 15; + + /** + * The Constant EMOTE_SNOWMAN. + */ + public static final int EMOTE_SNOWMAN = 16; + + /** + * The Constant EMOTE_AIR_GUITAR. + */ + public static final int EMOTE_AIR_GUITAR = 17; + + /** + * The Constant EMOTE_SAFETY_FIRST. + */ + public static final int EMOTE_SAFETY_FIRST = 18; + + /** + * The Constant EMOTE_EXPLORE. + */ + public static final int EMOTE_EXPLORE = 19; + + /** + * The Constant BIG_GP_EMOTES. + */ + public static final int[] BIG_GP_EMOTES = { EMOTE_GLASS_WALL, EMOTE_GLASS_BOX, EMOTE_CLIMB_ROPE, EMOTE_LEAN, EMOTE_SCARED, EMOTE_ZOMBIE_WALK, EMOTE_ZOMBIE_DANCE, EMOTE_BUNNY_HOP, EMOTE_SKILL_CAPE, EMOTE_SNOWMAN, EMOTE_AIR_GUITAR, EMOTE_SAFETY_FIRST, EMOTE_EXPLORE }; + + /** + * The Constant LITTLE_GP_EMOTES. + */ + public static final int[] LITTLE_GP_EMOTES = { EMOTE_FLAP, EMOTE_SLAP_HEAD, EMOTE_IDEA, EMOTE_STOMP }; + + /** + * The Constant GOBLIN_EMOTES. + */ + public static final int[] GOBLIN_EMOTES = { EMOTE_GOBLIN_BOW, EMOTE_GOBLIN_SALUTE }; + + /** + * The Constant AUTO_UNLOCKED_EMOTES. + */ + public static final int AUTO_UNLOCKED_EMOTES = 22; + + /** + * The Constant SKILL_CAPE_INFO. + */ + public static final int[][] SKILL_CAPE_INFO = { { 9747, 4959, 823 }, { 9753, 4961, 824 }, { 9750, 4981, 828 }, { 9768, 4971, 833 }, { 9756, 4973, 832 }, { 9759, 4979, 829 }, { 9762, 4939, 813 }, { 9801, 4955, 821 }, { 9807, 4957, 822 }, { 9783, 4937, 812 }, { 9798, 4951, 819 }, { 9804, 4975, 831 }, { 9780, 4949, 818 }, { 9795, 4943, 815 }, { 9792, 4941, 814 }, { 9774, 4969, 835 }, { 9771, 4977, 830 }, { 9777, 4965, 826 }, { 9786, 4967, 1656 }, { 9810, 4963, 825 }, { 9765, 4947, 817 }, { 9948, 5158, 907 }, { 9789, 4953, 820 }, { 12169, 8525, 1515 }, { 9813, 4945, 816 } }; + +} diff --git a/src/osiris/util/StopWatch.java b/src/osiris/util/StopWatch.java new file mode 100644 index 0000000..47a40c5 --- /dev/null +++ b/src/osiris/util/StopWatch.java @@ -0,0 +1,82 @@ +package osiris.util; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import osiris.ServerEngine; + +// TODO: Auto-generated Javadoc + +/** + * The Class StopWatch. + * + * @author Boomer + * + */ +public class StopWatch { + + /** + * The start ticks. + */ + private int startTicks; + + /** + * Instantiates a new tick timer. + */ + public StopWatch() { + this.startTicks = ServerEngine.getTicks(); + } + + /** + * Sets the ticks. + * + * @param ticks + * the ticks + * @return the stop watch + */ + public StopWatch setTicks(int ticks) { + startTicks = ServerEngine.getTicks() - ticks; + return this; + } + + /** + * Elapsed. + * + * @return the int + */ + public int elapsed() { + return ServerEngine.getTicks() - startTicks; + } + + /** + * Reset. + */ + public void reset() { + this.startTicks = ServerEngine.getTicks(); + } + + /** + * Gets the start. + * + * @return the start + */ + public int getStart() { + return startTicks; + } + +} diff --git a/src/osiris/util/TickTimer.java b/src/osiris/util/TickTimer.java new file mode 100644 index 0000000..efd90fb --- /dev/null +++ b/src/osiris/util/TickTimer.java @@ -0,0 +1,70 @@ +package osiris.util; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.util.concurrent.TimeUnit; + +// TODO: Auto-generated Javadoc +/** + * The Class TickTimer. + * + * @author Boomer + */ +public class TickTimer { + + /** The ticks required. */ + private long ticksRequired; + + /** The stop watch. */ + private StopWatch stopWatch; + + /** + * Instantiates a new tick timer. + */ + public TickTimer() { + this.stopWatch = new StopWatch(); + } + + /** + * Sets the delay. + * + * @param time + * the time + * @param unit + * the unit + * @return the tick timer + */ + public TickTimer setDelay(long time, TimeUnit unit) { + if (unit != null) + this.ticksRequired = Utilities.secondsToTicks(unit.toSeconds(time)); + else + this.ticksRequired = time; + stopWatch.reset(); + return this; + } + + /** + * Completed. + * + * @return true, if successful + */ + public boolean completed() { + return this.stopWatch.elapsed() >= ticksRequired; + } +} diff --git a/src/osiris/util/Utilities.java b/src/osiris/util/Utilities.java new file mode 100644 index 0000000..8d1c186 --- /dev/null +++ b/src/osiris/util/Utilities.java @@ -0,0 +1,533 @@ +package osiris.util; + +/* + * Osiris Emulator + * Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Random; + +import osiris.game.model.Position; +import osiris.io.PacketReader; + +// TODO: Auto-generated Javadoc + +/** + * A collection of utility methods. + * + * @author Blake + * + */ +public class Utilities { + + /** + * Gets the average. + * + * @param numbers + * the numbers + * @return the average + */ + public static int getAverage(int... numbers) { + int total = 0; + for (int i : numbers) { + total += i; + } + return total / numbers.length; + } + + /** + * Proper case. + * + * @param str + * the str + * @return the string + */ + public static String toProperCase(String str) { + return (str.substring(0, 1).toUpperCase() + str.substring(1).toLowerCase()); + } + + /** + * Level for exp. + * + * @param exp + * the exp + * @return the int + */ + public static int levelForExp(double exp) { + int points = 0; + int output = 0; + for (int lvl = 1; lvl < 100; lvl++) { + points += Math.floor((double) lvl + 300.0 * Math.pow(2.0, (double) lvl / 7.0)); + output = (int) Math.floor(points / 4); + if ((output - 1) >= exp) { + return lvl; + } + } + return 99; + } + + /** + * Ticks to mins. + * + * @param ticks + * the ticks + * @return the int + */ + public static int ticksToMins(int ticks) { + return (ticks * 600 / 1000 / 60) + 1; + } + + /** + * Exp for level. + * + * @param level + * the level + * @return the double + */ + public static double expForLevel(int level) { + double points = 0; + double 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; + } + + /** + * String to long. + * + * @param s + * the s + * @return the long + */ + public static long stringToLong(String s) { + long l = 0L; + for (int i = 0; i < s.length() && i < 12; i++) { + char c = s.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; + } + while (l % 37L == 0L && l != 0L) + l /= 37L; + return l; + } + + /** + * Long to string. + * + * @param l + * the l + * @return the string + */ + public static String longToString(long l) { + return longToString(l, true); + } + + /** + * Long to string. + * + * @param l + * the l + * @param proper + * the proper + * @return the string + */ + public static String longToString(long l, boolean proper) { + if (l <= 0L || l >= 0x5b5b57f8a98a5dd1L) { + return null; + } + if (l % 37L == 0L) { + return null; + } + int i = 0; + char ac[] = new char[12]; + while (l != 0L) { + long l1 = l; + l /= 37L; + ac[11 - i++] = VALID_CHARS[(int) (l1 - l * 37L)]; + } + String out = new String(ac, 12 - i, i); + return out; + } + + /** + * Encrypt player chat. + * + * @param is + * the is + * @param i_25_ + * the i_25_ + * @param i_26_ + * the i_26_ + * @param i_27_ + * the i_27_ + * @param is_28_ + * the is_28_ + * @return the int + */ + public static int encryptPlayerChat(byte[] is, int i_25_, int i_26_, int i_27_, byte[] is_28_) { + try { + i_27_ += i_25_; + int i_29_ = 0; + int i_30_ = i_26_ << -2116795453; + for (; i_27_ > i_25_; i_25_++) { + int i_31_ = 0xff & is_28_[i_25_]; + int i_32_ = anIntArray233[i_31_]; + int i_33_ = aByteArray235[i_31_]; + int i_34_ = i_30_ >> -1445887805; + int i_35_ = i_30_ & 0x7; + i_29_ &= (-i_35_ >> 473515839); + i_30_ += i_33_; + int i_36_ = ((-1 + (i_35_ - -i_33_)) >> -1430991229) + i_34_; + i_35_ += 24; + is[i_34_] = (byte) (i_29_ = (i_29_ | (i_32_ >>> i_35_))); + if ((i_36_ ^ 0xffffffff) < (i_34_ ^ 0xffffffff)) { + i_34_++; + i_35_ -= 8; + is[i_34_] = (byte) (i_29_ = i_32_ >>> i_35_); + if (i_36_ > i_34_) { + i_34_++; + i_35_ -= 8; + is[i_34_] = (byte) (i_29_ = i_32_ >>> i_35_); + if (i_36_ > i_34_) { + i_35_ -= 8; + i_34_++; + is[i_34_] = (byte) (i_29_ = i_32_ >>> i_35_); + if (i_34_ < i_36_) { + i_35_ -= 8; + i_34_++; + is[i_34_] = (byte) (i_29_ = i_32_ << -i_35_); + } + } + } + } + } + return -i_26_ + ((7 + i_30_) >> -662855293); + } catch (RuntimeException runtimeexception) { + } + return 0; + } + + /** + * Decrypt player chat. + * + * @param str + * the str + * @param totalChars + * the total chars + * @return the string + */ + public static String decryptPlayerChat(PacketReader str, int totalChars) { + try { + if (totalChars == 0) { + return ""; + } + int charsDecoded = 0; + int i_4_ = 0; + String s = ""; + for (;;) { + byte i_7_ = (byte) str.readByte(); + if (i_7_ >= 0) { + i_4_++; + } else { + i_4_ = anIntArray241[i_4_]; + } + int i_8_; + if ((i_8_ = anIntArray241[i_4_]) < 0) { + s += (char) (byte) (i_8_ ^ 0xffffffff); + if (totalChars <= ++charsDecoded) { + break; + } + i_4_ = 0; + } + if (((i_7_ & 0x40) ^ 0xffffffff) != -1) { + i_4_ = anIntArray241[i_4_]; + } else { + i_4_++; + } + if ((i_8_ = anIntArray241[i_4_]) < 0) { + s += (char) (byte) (i_8_ ^ 0xffffffff); + if (++charsDecoded >= totalChars) { + break; + } + i_4_ = 0; + } + if ((0x20 & i_7_) == 0) { + i_4_++; + } else { + i_4_ = anIntArray241[i_4_]; + } + if ((i_8_ = anIntArray241[i_4_]) < 0) { + s += (char) (byte) (i_8_ ^ 0xffffffff); + if (totalChars <= ++charsDecoded) { + break; + } + i_4_ = 0; + } + if (((0x10 & i_7_) ^ 0xffffffff) == -1) { + i_4_++; + } else { + i_4_ = anIntArray241[i_4_]; + } + if ((i_8_ = anIntArray241[i_4_]) < 0) { + s += (char) (byte) (i_8_ ^ 0xffffffff); + if (totalChars <= ++charsDecoded) { + break; + } + + i_4_ = 0; + } + if (((0x8 & i_7_) ^ 0xffffffff) != -1) { + i_4_ = anIntArray241[i_4_]; + } else { + i_4_++; + } + if ((i_8_ = anIntArray241[i_4_]) < 0) { + s += (char) (byte) (i_8_ ^ 0xffffffff); + if (++charsDecoded >= totalChars) { + break; + } + i_4_ = 0; + } + if ((0x4 & i_7_) == 0) { + i_4_++; + } else { + i_4_ = anIntArray241[i_4_]; + } + if ((i_8_ = anIntArray241[i_4_]) < 0) { + s += (char) (byte) (i_8_ ^ 0xffffffff); + if (totalChars <= ++charsDecoded) { + break; + } + i_4_ = 0; + } + if (((i_7_ & 0x2) ^ 0xffffffff) != -1) { + i_4_ = anIntArray241[i_4_]; + } else { + i_4_++; + } + if ((i_8_ = anIntArray241[i_4_]) < 0) { + s += (char) (byte) (i_8_ ^ 0xffffffff); + if (totalChars <= ++charsDecoded) { + break; + } + i_4_ = 0; + } + if (((i_7_ & 0x1) ^ 0xffffffff) != -1) { + i_4_ = anIntArray241[i_4_]; + } else { + i_4_++; + } + if ((i_8_ = anIntArray241[i_4_]) < 0) { + s += (char) (byte) (i_8_ ^ 0xffffffff); + if (++charsDecoded >= totalChars) { + break; + } + i_4_ = 0; + } + } + return s; + } catch (RuntimeException runtimeexception) { + } + return ""; + } + + /** + * Gets the reverse. + * + * @param src + * the src + * @return the reverse + */ + public static String getReverse(String src) { + int i, len = src.length(); + StringBuffer reversed = new StringBuffer(len); + for (i = (len - 1); i >= 0; i--) + reversed.append(src.charAt(i)); + return reversed.toString(); + } + + /** + * The an int array233. + */ + private static int[] anIntArray233 = { 0, 1024, 2048, 3072, 4096, 5120, 6144, 8192, 9216, 12288, 10240, 11264, 16384, 18432, 17408, 20480, 21504, 22528, 23552, 24576, 25600, 26624, 27648, 28672, 29696, 30720, 31744, 32768, 33792, 34816, 35840, 36864, 536870912, 16777216, 37888, 65536, 38912, 131072, 196608, 33554432, 524288, 1048576, 1572864, 262144, 67108864, 4194304, 134217728, 327680, 8388608, 2097152, 12582912, 13631488, 14680064, 15728640, 100663296, 101187584, 101711872, 101974016, 102760448, 102236160, 40960, 393216, 229376, 117440512, 104857600, 109051904, 201326592, 205520896, 209715200, 213909504, 106954752, 218103808, 226492416, 234881024, 222298112, 224395264, 268435456, 272629760, 276824064, 285212672, 289406976, 223346688, 293601280, 301989888, 318767104, 297795584, + 298844160, 310378496, 102498304, 335544320, 299892736, 300941312, 301006848, 300974080, 39936, 301465600, 49152, 1073741824, 369098752, 402653184, 1342177280, 1610612736, 469762048, 1476395008, -2147483648, -1879048192, 352321536, 1543503872, -2013265920, -1610612736, -1342177280, -1073741824, -1543503872, 356515840, -1476395008, -805306368, -536870912, -268435456, 1577058304, -134217728, 360710144, -67108864, 364904448, 51200, 57344, 52224, 301203456, 53248, 54272, 55296, 56320, 301072384, 301073408, 301074432, 301075456, 301076480, 301077504, 301078528, 301079552, 301080576, 301081600, 301082624, 301083648, 301084672, 301085696, 301086720, 301087744, 301088768, 301089792, 301090816, 301091840, 301092864, 301093888, 301094912, 301095936, 301096960, 301097984, 301099008, + 301100032, 301101056, 301102080, 301103104, 301104128, 301105152, 301106176, 301107200, 301108224, 301109248, 301110272, 301111296, 301112320, 301113344, 301114368, 301115392, 301116416, 301117440, 301118464, 301119488, 301120512, 301121536, 301122560, 301123584, 301124608, 301125632, 301126656, 301127680, 301128704, 301129728, 301130752, 301131776, 301132800, 301133824, 301134848, 301135872, 301136896, 301137920, 301138944, 301139968, 301140992, 301142016, 301143040, 301144064, 301145088, 301146112, 301147136, 301148160, 301149184, 301150208, 301151232, 301152256, 301153280, 301154304, 301155328, 301156352, 301157376, 301158400, 301159424, 301160448, 301161472, 301162496, 301163520, 301164544, 301165568, 301166592, 301167616, 301168640, 301169664, 301170688, 301171712, + 301172736, 301173760, 301174784, 301175808, 301176832, 301177856, 301178880, 301179904, 301180928, 301181952, 301182976, 301184000, 301185024, 301186048, 301187072, 301188096, 301189120, 301190144, 301191168, 301193216, 301195264, 301194240, 301197312, 301198336, 301199360, 301201408, 301202432 }; + + /** + * The a byte array235. + */ + private static byte[] aByteArray235 = { 22, 22, 22, 22, 22, 22, 21, 22, 22, 20, 22, 22, 22, 21, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 3, 8, 22, 16, 22, 16, 17, 7, 13, 13, 13, 16, 7, 10, 6, 16, 10, 11, 12, 12, 12, 12, 13, 13, 14, 14, 11, 14, 19, 15, 17, 8, 11, 9, 10, 10, 10, 10, 11, 10, 9, 7, 12, 11, 10, 10, 9, 10, 10, 12, 10, 9, 8, 12, 12, 9, 14, 8, 12, 17, 16, 17, 22, 13, 21, 4, 7, 6, 5, 3, 6, 6, 5, 4, 10, 7, 5, 6, 4, 4, 6, 10, 5, 4, 4, 5, 7, 6, 10, 6, 10, 22, 19, 22, 14, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 21, 22, 21, 22, 22, 22, 21, 22, 22 }; + + /** + * The an int array241. + */ + private static int[] anIntArray241 = { 215, 203, 83, 158, 104, 101, 93, 84, 107, 103, 109, 95, 94, 98, 89, 86, 70, 41, 32, 27, 24, 23, -1, -2, 26, -3, -4, 31, 30, -5, -6, -7, 37, 38, 36, -8, -9, -10, 40, -11, -12, 55, 48, 46, 47, -13, -14, -15, 52, 51, -16, -17, 54, -18, -19, 63, 60, 59, -20, -21, 62, -22, -23, 67, 66, -24, -25, 69, -26, -27, 199, 132, 80, 77, 76, -28, -29, 79, -30, -31, 87, 85, -32, -33, -34, -35, -36, 197, -37, 91, -38, 134, -39, -40, -41, 97, -42, -43, 133, 106, -44, 117, -45, -46, 139, -47, -48, 110, -49, -50, 114, 113, -51, -52, 116, -53, -54, 135, 138, 136, 129, 125, 124, -55, -56, 130, 128, -57, -58, -59, 183, -60, -61, -62, -63, -64, 148, -65, -66, 153, 149, 145, 144, -67, -68, 147, -69, -70, -71, 152, 154, -72, -73, -74, 157, 171, -75, -76, 207, 184, 174, + 167, 166, 165, -77, -78, -79, 172, 170, -80, -81, -82, 178, -83, 177, 182, -84, -85, 187, 181, -86, -87, -88, -89, 206, 221, -90, 189, -91, 198, 254, 262, 195, 196, -92, -93, -94, -95, -96, 252, 255, 250, -97, 211, 209, -98, -99, 212, -100, 213, -101, -102, -103, 224, -104, 232, 227, 220, 226, -105, -106, 246, 236, -107, 243, -108, -109, 231, 237, 235, -110, -111, 239, 238, -112, -113, -114, -115, -116, 241, -117, 244, -118, -119, 248, -120, 249, -121, -122, -123, 253, -124, -125, -126, -127, 259, 258, -128, -129, 261, -130, -131, 390, 327, 296, 281, 274, 271, 270, -132, -133, 273, -134, -135, 278, 277, -136, -137, 280, -138, -139, 289, 286, 285, -140, -141, 288, -142, -143, 293, 292, -144, -145, 295, -146, -147, 312, 305, 302, 301, -148, -149, 304, -150, -151, 309, 308, -152, + -153, 311, -154, -155, 320, 317, 316, -156, -157, 319, -158, -159, 324, 323, -160, -161, 326, -162, -163, 359, 344, 337, 334, 333, -164, -165, 336, -166, -167, 341, 340, -168, -169, 343, -170, -171, 352, 349, 348, -172, -173, 351, -174, -175, 356, 355, -176, -177, 358, -178, -179, 375, 368, 365, 364, -180, -181, 367, -182, -183, 372, 371, -184, -185, 374, -186, -187, 383, 380, 379, -188, -189, 382, -190, -191, 387, 386, -192, -193, 389, -194, -195, 454, 423, 408, 401, 398, 397, -196, -197, 400, -198, -199, 405, 404, -200, -201, 407, -202, -203, 416, 413, 412, -204, -205, 415, -206, -207, 420, 419, -208, -209, 422, -210, -211, 439, 432, 429, 428, -212, -213, 431, -214, -215, 436, 435, -216, -217, 438, -218, -219, 447, 444, 443, -220, -221, 446, -222, -223, 451, 450, -224, -225, + 453, -226, -227, 486, 471, 464, 461, 460, -228, -229, 463, -230, -231, 468, 467, -232, -233, 470, -234, -235, 479, 476, 475, -236, -237, 478, -238, -239, 483, 482, -240, -241, 485, -242, -243, 499, 495, 492, 491, -244, -245, 494, -246, -247, 497, -248, 502, -249, 506, 503, -250, -251, 505, -252, -253, 508, -254, 510, -255, -256, 0 }; + + /** + * The valid chars. + */ + public static char[] VALID_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' }; + + /** + * Gets the distance. + * + * @param a + * the a + * @param b + * the b + * @return the distance + */ + public static int getDistance(Position a, Position b) { + int deltaX = b.getX() - a.getX(); + int deltaY = b.getY() - a.getY(); + return ((int) Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2))); + } + + /** + * Random. + * + * @param high + * the high + * @return the int + */ + public static int random(int high) { + return new Random().nextInt(high + 1); + } + + /** + * Seconds to ticks. + * + * @param seconds + * the seconds + * @return the long + */ + public static long secondsToTicks(long seconds) { + return seconds * 1000 / 600; + } + + /** + * Generate positions in box. + * + * @param minX + * the min x + * @param minY + * the min y + * @param maxX + * the max x + * @param maxY + * the max y + * @return the array list + */ + public static ArrayList generatePositionsInBox(int minX, int minY, int maxX, int maxY) { + ArrayList temp = new ArrayList(); + int countX = maxX - minX; + int countY = maxY - minY; + for (int x = 0; x <= countX; x++) { + for (int y = 0; y <= countY; y++) { + int posX = minX + x; + int posY = minY + y; + temp.add(new Position(posX, posY)); + } + } + return temp; + } + + /** + * Smf hash. + * + * @param username + * the username + * @param salt + * the salt + * @return the string + */ + public static String smfHash(String username, Integer salt) { + try { + return SHA(SHA(username) + salt); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + return null; + } + + /** + * SHA. + * + * @param text + * the text + * @return the string + * @throws NoSuchAlgorithmException + * the no such algorithm exception + * @throws UnsupportedEncodingException + * the unsupported encoding exception + */ + public static String SHA(String text) throws NoSuchAlgorithmException, UnsupportedEncodingException { + MessageDigest md = MessageDigest.getInstance("SHA"); + byte[] md5hash; + md.update(text.getBytes("iso-8859-1"), 0, text.length()); + md5hash = md.digest(); + return convertToHex(md5hash); + } + + /** + * Convert to hex. + * + * @param data + * the data + * @return the string + */ + private static String convertToHex(byte[] data) { + StringBuffer buf = new StringBuffer(); + for (int i = 0; i < data.length; i++) { + int halfbyte = (data[i] >>> 4) & 0x0F; + int two_halfs = 0; + do { + if ((0 <= halfbyte) && (halfbyte <= 9)) + buf.append((char) ('0' + halfbyte)); + else + buf.append((char) ('a' + (halfbyte - 10))); + halfbyte = data[i] & 0x0F; + } while (two_halfs++ < 1); + } + return buf.toString(); + } + + public static int generateSalt() { + return new SecureRandom().nextInt(); + } +} diff --git a/src/ruby/bootstrap.rb b/src/ruby/bootstrap.rb new file mode 100644 index 0000000..27ad391 --- /dev/null +++ b/src/ruby/bootstrap.rb @@ -0,0 +1,20 @@ +# Osiris Emulator +# Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +# A script which provides utility functions to "glue" code between Java and Ruby. +# Author: Blake + +# TODO: Utility defs... \ No newline at end of file diff --git a/src/ruby/content/dialogue_options.rb b/src/ruby/content/dialogue_options.rb new file mode 100644 index 0000000..a6a4bc9 --- /dev/null +++ b/src/ruby/content/dialogue_options.rb @@ -0,0 +1,75 @@ +# Osiris Emulator +# Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +require 'java' + +java_import 'osiris.Main' +java_import 'osiris.game.action.impl.DialogueAction' +java_import 'osiris.game.action.impl.BankAction' + +def dialogueOption(player, id) + dialogue = player.get_currently_open_dialogue + if id == 2 + if dialogue.get_npc == 494 + BankAction.new(player) + elsif dialogue.get_npc == -2 + move = get_position_change(dialogue.get_next) + player.teleport(move.get_up) + end_conversation(player, dialogue) + else continue_conversation(player, dialogue) end + else + if dialogue.get_npc == 304 + player.get_current_action.cancel + return + elsif dialogue.get_npc == -2 + move = get_position_change(dialogue.get_next) + player.teleport(move.get_down) + end_conversation(player, dialogue) + else continue_conversation(player, dialogue) end + end +end + +def end_conversation(player, dialogue) + if player.get_current_action.kind_of? DialogueAction then + player.get_current_action.cancel + else return end +end + +def continue_conversation(player, dialogue) + if dialogue.get_next == -1 + end_conversation(player, dialogue) + else + DialogueAction.new(player, get_next_dialogue(dialogue.get_next, dialogue.get_npc)).run + end +end + +def get_next_dialogue(id, npc) + Main.get_dialogues.length.times do |i| + dialogue = Main.get_dialogues.get(i) + if dialogue.get_id == id and dialogue.get_npc == npc + return Main.get_dialogues.get(i) + end + end +end + +def get_position_change(id) + Main.get_stairs.length.times do |i| + info = Main.get_stairs.get(i) + if info.get_id == id + return Main.get_stairs.get(i) + end + end +end diff --git a/src/ruby/content/potion_drinking.rb b/src/ruby/content/potion_drinking.rb new file mode 100644 index 0000000..8bcf532 --- /dev/null +++ b/src/ruby/content/potion_drinking.rb @@ -0,0 +1,167 @@ +# Osiris Emulator +# Copyright (C) 2011 Garrett Woodard, Blake Beaupain, Travis Burtrum +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +require 'java' +java_import 'osiris.game.model.item.Item' +java_import 'osiris.game.model.def.ItemDef' +java_import 'osiris.game.model.effect.PoisonEffect' +java_import 'osiris.game.action.ItemActionListener' +java_import 'osiris.game.action.ItemActionListeners' +java_import 'osiris.game.action.item.ItemClickAction' +java_import 'osiris.game.update.block.AnimationBlock' + +# Next potion ID's. +POTIONS = Hash.new + +# Strength potions +POTIONS[113] = 115 +POTIONS[115] = 117 +POTIONS[117] = 119 + +# Attack potions +POTIONS[2428] = 121 +POTIONS[121] = 123 +POTIONS[123] = 125 + +# Restore potions +POTIONS[2430] = 127 +POTIONS[127] = 129 +POTIONS[129] = 131 + +# Defence potions +POTIONS[2432] = 133 +POTIONS[133] = 135 +POTIONS[135] = 137 + +# Prayer potions +POTIONS[2434] = 139 +POTIONS[139] = 141 +POTIONS[141] = 143 + +# Ranging potions +POTIONS[2444] = 169 +POTIONS[169] = 171 +POTIONS[171] = 173 + +# Energy potions +POTIONS[3008] = 3010 +POTIONS[3010] = 3012 +POTIONS[3012] = 3014 + +# Antipoison potions +POTIONS[2446] = 175 +POTIONS[175] = 177 +POTIONS[177] = 179 + +# Magic potions +POTIONS[3040] = 3042 +POTIONS[3042] = 3044 +POTIONS[3044] = 3046 + +# Super attack potions +POTIONS[2436] = 145 +POTIONS[145] = 147 +POTIONS[147] = 149 + +# Super strength potions +POTIONS[2440] = 157 +POTIONS[157] = 159 +POTIONS[159] = 161 + +# Super defence potions +POTIONS[2442] = 163 +POTIONS[163] = 165 +POTIONS[165] = 167 + +class PotionListener + include ItemActionListener + + # Gets the next (i.e. 1 lower dosage) potion ID for an item. + def get_next_id item + if ItemDef.for_id(item.get_id).get_name.include? '(1)' then return 229 + else return POTIONS[item.get_id] end + end + + def apply_effect player, id + name = ItemDef.for_id(id).get_name.downcase! + if name.include? 'poison' + poisoned = true + elsif name.include? 'defence' + stat = 1 + elsif name.include? 'attack' + stat = 0 + elsif name.include? 'strength' + stat = 2 + elsif name.include? 'magic' + stat = 6 + elsif name.include? 'ranging' + stat = 4 + elsif name.include? 'prayer' + stat = 5 + end + + skills = player.get_skills + if poisoned + player.get_effects.size.times do |i| + if player.get_effects.get(i).kind_of? PoisonEffect + player.get_effects.get(i).cancel + end + end + else + if name.include? 'super' + modifier = ((skills.max_level(stat) * 15) / 100) + adjustment = 5 + modifier + else + if stat == 5 + modifier = (skills.max_level(stat) / 4) + adjustment = 7 + modifier + else + modifier = ((skills.max_level(stat) * 10) / 100) + adjustment = 3 + modifier + end + end + + if skills.current_level(stat) >= 99 + if stat == 5 + skills.set_cur_level(stat, 99) + return end + skills.set_cur_level(stat, (skills.max_level(stat) + adjustment)) + else + if stat == 5 and (skills.current_level(stat) + adjustment) >= skills.max_level(stat) + skills.set_cur_level(stat, skills.max_level(stat)) + else + skills.set_cur_level(stat, (skills.current_level(stat) + adjustment)) + end + end + end + end + + # Called when a player drinks a potion. + def on_item_action action + player = action.get_character + if action.kind_of? ItemClickAction then + player.get_event_writer.send_message 'You drink some of the potion.' + player.add_update_block(AnimationBlock.new(player, 829, 0)); + player.get_inventory.remove_by_slot action.get_slot, 1 + player.get_inventory.add Item.new get_next_id(action.get_item) + apply_effect action.get_player, action.get_item.get_id + end + end +end + +# Register the potion listener. +ItemActionListeners.add_listener PotionListener.new, +113, 115, 117, 119, 2428, 121, 123, 125, 2430, 127, 129, 131, 2432, 133, 135, 137, 2434, 139, 141, 143, 2444, 169, 171, 173, 3008, 3010, 3012, 3014, 3040, 3042, 3044, 3046, 2436, 145, 147, 149, 2440, 157, 159, 161, 2442, 163, 165, 167, 2446, 175, 177, 179